diff --git a/.editorconfig b/.editorconfig index 892b63c33c..f731f7194a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ -root = true +# root = true # Unix-style newlines with a newline ending every file -[*] -indent_style = tab -indent_size = 4 +# [*] +# indent_style = tab +# indent_size = 4 diff --git a/.gitignore b/.gitignore index 336b64107a..118d11be55 100644 --- a/.gitignore +++ b/.gitignore @@ -320,3 +320,4 @@ Kopie von* src/SmartStoreNET.Packager.sln Log.txt +src/Presentation/SmartStore.Web/Themes/FlexMuseo/ diff --git a/CREDITS.txt b/CREDITS.txt index e421722989..5ea4c4a6fb 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -52,11 +52,11 @@ Copyright: Copyright Andrey Taritsyn 2014 License: Apache License 2.0 (Apache) -CKEditor +summernote --------------------------------------------- -WebSite: http://ckeditor.com/ -Copyright: © 2014 CKSource - Frederico Knabben -License: GNU Library General Public License (LGPL) +WebSite: https://summernote.org/ +Copyright: Copyright (c) 2015~ Summernote Team (https://github.com/orgs/summernote/people) +License: MIT DotNetOpenAuth diff --git a/README.md b/README.md index bd0524f866..aa25d27b32 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * Unlimited number of products and categories * Product Bundles * RESTful WebApi -* Multi-language support +* Multi-language and RTL support * Modern, clean, SEO-optimized and fully responsive Theme based on Bootstrap 4 * Ultra fast search framework with faceted search support * Extremely scalable thanks to output caching, REDIS & Microsoft Azure support. @@ -56,22 +56,23 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * and many more... ## Project Status -SmartStore.NET V3.0.0 has been released on May 15, 2017. The highlights are: - -* **Flex**: New mobile-first responsive Theme based on Bootstrap 4 -* **Mega Search \***: highly professional search framework based on Lucene.NET - * Ultra fast search results, even with millions of items - * Faceted search - * Synonyms - * Compound word splitting -* **Mega Menu \***: highly customizable catalog menu widgets -* **Content Slider \***: creates eye-catching content to boost sales -* **Output Cache \*** with "donut hole caching" for maximum speed and scalability -* **Microsoft AZURE \*** provider for media storage -* Web Farms: **REDIS \*** providers for Business Cache, Output Cache and Session State -* Product variant **option sets** -* New product specification attribute type: **numeric range** -* Image support for variant attributes +SmartStore.NET V3.1.0 has been released on April 20, 2018. The highlights are: + +* **Wallet \***: Enables full or partial order payment via credit account. Includes REST-Api. +* **[Liquid](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers) template engine**: very flexible templating for e-mails and campaigns with autocompletion and syntax highlighting. +* **Cash Rounding**: define money rounding rules on currency and payment method level. +* **Modern, responsive backend**: migrated backend to Bootstrap 4, overhauled and improved the user interface. +* **Enhanced MegaMenu \***: virtual dropdowns for surplus top-level categories and brands. +* **RTL**: comprehensive RTL (Right-to-left) and bidi(rectional) support. +* **Amazon Pay**: + * Supports merchants registered in the USA and Japan + * External authentication via *Login with Amazon* button in shop frontend + * Several improvements through the new *Login and pay with Amazon* services +* **Image processing**: new processing and caching strategy! Thumbnails are not created synchronously during the main request anymore, instead a new middleware route defers processing until an image is requested by any client. +* **TinyImage \***: scores ultra-high image compression rates (up to 80 %!) and enables WebP support. +* **UrlRewriter \***: define URL redirection rules in the backend using *mod_rewrite* notation. +* **Address formatting** templates by country +* **Language packs**: downloader & auto-importer for packages available online. * ...and a lot more new features, enhancements and fixes \* Commercial plugin @@ -105,4 +106,4 @@ NOTE: SmartStore.NET 3 requires [Visual C++ Redistributable für Visual Studio 2 ## License -SmartStore.NET is released under the [GPLv3 license](http://www.gnu.org/licenses/gpl-3.0.txt). +SmartStore.NET Community Edition is released under the [GPLv3 license](http://www.gnu.org/licenses/gpl-3.0.txt). diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets index dd6a1a1895..fec484de68 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -39,7 +39,7 @@ x86 $(BUILD_NUMBER) - 3.0.3 + 3.1.5 $(StageFolder) .$(Version) diff --git a/changelog.md b/changelog.md index 7b16444611..c29e6d43f7 100644 --- a/changelog.md +++ b/changelog.md @@ -1,32 +1,153 @@ # Release Notes +## SmartStore.NET 3.2 + +### New Features +* #1144 Enable multi server search index +* Made Topic ACL enabled +* Implemented paging & filtering for Topic grid +* Topics: added **IsPublished**, **Short Title** (link text) and **Intro** (teaser) properties. +* New security option: Use invisible reCAPTCHA +* **BeezUp**: + * #1459 Add option to only submit one category name per product + * Allow to specify export categories per product +* Wallet: Allow customer to choose whether refund should be submitted to his wallet. +* Added option to display preview pictures in product lists +* Added option to add multiple file versions to product download section +* Added options for alternating price display (in badges) +* Added option to display a captcha on forum pages when creating or replying to a topic. +* MegaSearch: Supports searching for forum posts. +* Customer avatar: Letter with colored background if no avatar image was uploaded. + +### Improvements +* (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). +* Debitoor: Partially update customer instead of full update to avoid all fields being overwritten +* #1479 Show in messages the delivery time at the time of purchase + +### Bugfixes +* In a multi-store environment, multiple topics with the same system name cannot be resolved reliably. +* **GMC**: + * Export the product images if no attribute images are defined + * Do not export the first image twice for additional images + * Export image URL of full size image (not default size) for additional images +* Media middleware: 0-byte files should be treated as missing. +* Megamenu alpha/omega blends do not toggle correctly on touch devices +* Summernote HTML editor exceeds parent container width when CodeMirror is activated +* Only display a zero search hits warning if at least one filter is activated +* #1436 Do not display delivery time in customer order completed messages +* "ArgumentNullException: The value must not be NULL" if a topic is password protected +* Tax by region: Fixes after inserting a tax rate country column shows "Unavailable" +* #1014 Switching to default language keeps specific URL alias of current page +* Shipping by total: Fixes when inserting a record the country was not saved +* #1460 Editing of the customer title is missing on customer and address pages in the backend +* #1447 Checkout button payment methods (Amazon, PayPal Express) won't work in conjunction with mandatory checkout attributes +* When creating a topic, the widget zone input shows System.String[] +* Switching the language always redirected to the home page if SEO friendly URLs was deactivated. +* File upload of a checkout attribute was not stored on cart page. +* Redirecting within checkout may have displayed an incorrect URL in the browser. +* Server cannot modify cookies after HTTP headers have been sent. +* Wrong base price on product and cart page when a special price is active. +* In a multi-store, message templates may have loaded the wrong disclaimer and conditions-of-use text. +* NullReferenceException in manufacturer list when there is no manufacturer. +* Wrong order of featured products on category page. +* #1504 Cart item price calculation wrong if attribute combinations with text types are involved. + + +## SmartStore.NET 3.1.5 + +### Highlights +* Compliance with EU-GDPR requirements +* Search engine friendly topic URLs +* "Honeypot" bot detection for registration and contact forms. + +### New Features +* #1429 Search engine friendly topic URLs +* Implemented cookie consent according to EU-GDPR +* Added checkboxes for data processing consent in all relevant forms +* Implemented "Honeypot" bot detection for registration and contact forms. +* Trusted Shops: Added consent checkbox to confirm order page for submission of customer email address to Trusted Shops if review widget is active +* #1226 Shop-Connector: Added exchange of tier prices and delivery times +* #1439 Debitoor: Option whether to display the payment method and SKU on invoices + +### Improvements +* Added double opt-in feature for newsletter subscriptions during checkout (confirm order) +* Allow forward slash in product tag URL slug +* Theming: throttle AJAX cart refresh after spin up/down click +* Moved StoreLastIpAddress & DisplayPrivacyAgreementOnContactUs from customer settings to privacy settings tab +* #1450 Show the regular price only if it's higher than the final price +* #1450 Do not ignore discounts with a negative amount +* (Soft) deleted customers can be edited now +* Customer IP addresses will be anonymized on (soft) deletion +* Set catalogsettings.showsharebutton to false as its not compliant with GDPR +* Made form fields for first & last name in customer registration optional +* Implemented settings to make form fields for first & last name required again +* Made form field for full name in contact us & product request optional +* Implemented settings to make form field for full name in contact us & product request required again +* #1453 Import: Use [IGNORE] to ignore a field value on record level +* #1455 More detail on packing slip when bundled item +* Display category alias as badge in grids and dropdowns + +### Bugfixes +* Migration: take all same-named message templates into account +* Messaging: OrderPlaced e-mail templates show main product image even when an attribute combination with a custom image has been selected +* Theming: fix broken product review voting +* Theming: added missing bottom space to .html-editor-content +* Theming: Language switcher is not displayed if no currency options are available +* No bundle item thumbnail displayed in bundle summary if item is not individually visible +* Tracking number in shipment details was not saved +* Assigning or removing product tags did not invalidate model cache +* Reward points weren't displayed in message templates +* Dashboard: link for not yet shipped orders loads list with all orders +* Topic search button had no effect +* #1442 Message factory should not throw an exception if a template has been deactivated +* Fixes script error "$(...).tab is not a function" on product detail page +* Title attribute for the product name in product lists was sometimes truncated +* Relativizing font sizes should cast to double, not int +* Fixes category list on product edit page shows empty category name +* #1438 Debitoor: The country is displayed twice +* MegaSearch: Fixes indexing ignores DeliveryTimeIdForEmptyStock setting +* Web API: Fixes "No NavigationLink factory was found for the navigation property 'WalletHistory'" +* #1449 IgnoreCharges of shipping methods is not working if a localized name is specified +* Fixes "The object does not support the property or method 'startsWith'" on product edit page. +* Wallet: Fixes "Child actions are not allowed to perform redirect actions" when there are cart warnings +* Fixes the delivery time in the order notifications may differ from delivery time on the product detail page + + ## SmartStore.NET 3.1.0 -### Breaking changes -* Message template customizations are lost due to the new template engine. You have to customize the templates again. No automatic migration, sorry :-( -* Amazon Pay: The plugin has been changed to new *Login and pay with Amazon* services. A registration at Amazon and new access data are necessary for its use. The old access data can no longer be used. -* (Dev) Calls to cache methods `Keys()` and `RemoveByPattern()` require glob chars to be present now (supported glob-styles see [https://redis.io/commands/keys](https://redis.io/commands/keys)). Previously these methods appended `*` to the passed pattern, which made pattern matching rather unflexible. -* (Dev) Hook framework now passes `IHookedEntity` interface instead of `HookedEntity` class -* (Dev) Completely removed all `EntityInserted`, `EntityUpdated` and `EntityDeleted` legacy events. We were using DbSaveHooks anyway, which provides a much more powerful and way faster pub-sub mechanism for database operations. ### Highlights -* New [Liquid](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers) based template engine -* Multi-configurable rounding of order total ("cash rounding"). Can be adjusted and activated separately for each currency and payment method. -* (Perf) Picture service: new processing and caching strategy! Thumbnails are not created synchronously during the main request anymore, instead a new middleware route defers processing until an image is actually requested by any client. -* MegaMenu shrinker and *Brands* virtual menu item -* Address formatting templates by country -* Connection to translate.smartstore.com. For available languages, localized resources can be downloaded and installed directly. +* **Wallet**: Enables full or partial order payment via credit account. Includes REST-Api. (commercial plugin) +* **[Liquid](https://github.com/Shopify/liquid/wiki/Liquid-for-Designers) template engine**: very flexible templating for e-mails and campaigns with autocompletion and syntax highlighting. +* **Cash Rounding**: define money rounding rules on currency and payment method level. +* **Modern, responsive backend**: migrated backend to Bootstrap 4, overhauled and improved the user interface. +* **Enhanced MegaMenu**: virtual dropdowns for surplus top-level categories and brands (commercial plugin exclusively bundled with Pro Edition). +* **RTL**: comprehensive RTL (Right-to-left) and bidi(rectional) support. * **Amazon Pay**: * Supports merchants registered in the USA and Japan * External authentication via *Login with Amazon* button in shop frontend * Several improvements through the new *Login and pay with Amazon* services +* (Perf) **Faster image processing**: new processing and caching strategy! Thumbnails are not created synchronously during the main request anymore, instead a new middleware route defers processing until an image is requested by any client. +* **TinyImage**: scores ultra-high image compression rates (up to 80 %!) and enables WebP support (commercial plugin exclusively bundled with Premium Edition). +* **UrlRewriter**: define URL redirection rules in the backend using *mod_rewrite* notation. (commercial plugin) +* **Address formatting** templates by country +* **Language packs**: downloader & auto-importer for packages available online. + +### Breaking changes +* Message template customizations are lost due to the new template engine. You have to customize the templates again. No automatic migration, sorry :-( +* Amazon Pay: The plugin has been changed to new *Login and pay with Amazon* services. The client ID has been added, which has to be created in Amazon Seller Central and saved in the payment method configuration. +* (Dev) Calls to cache methods `Keys()` and `RemoveByPattern()` require glob chars to be present now (supported glob-styles see [https://redis.io/commands/keys](https://redis.io/commands/keys)). Previously these methods appended `*` to the passed pattern, which made pattern matching rather unflexible. +* (Dev) Hook framework now passes `IHookedEntity` interface instead of `HookedEntity` class +* (Dev) Completely removed all `EntityInserted`, `EntityUpdated` and `EntityDeleted` legacy events. We were using DbSaveHooks anyway, which provides a much more powerful and way faster pub-sub mechanism for database operations. ### New Features * 1203 MegaMenu shrinker and *Brands* virtual menu item +* [Summernote](https://summernote.org/) is now the primary HTML editor * #431 Added option to randomize the display order for slides on each request * #1258 Add option to filter shipping and payment methods by a specific customer role * #1247 Allow to import non system customer roles in customer import * #1117 Added an option to display a dropdown menu for manufacturers * #1203 Added an option to define a maximum number of elements in the main menu for the first hierarchy of the catalog navigation +* GMC: column chooser for edit grid * #1100 Customer can register in frontend via "Login with Amazon" button * **Web API**: * #1292 Added endpoint to get order in PDF format @@ -37,12 +158,18 @@ * #1295 Sales tracking (tracking pixel) for Billiger.de * XML and CSV export of shopping cart and wishlist items * #1363 Make storing of IP addresses optional +* #729 Option for automatic order amount capturing when the shipping status changed to "shipped" +* (Dev) ILocalizationFileResolver: responsible for finding localization files for client scripts +* #998 GMC: Find a way to map attribute combination values to feed export values +* Added Instagram icon to social media icons in footer ### Improvements * Target .NET Framework changed: 4.5.2 > 4.6.1. * Lower memory consumption * #649 Media FileSystem provider: segmenting files in subfolders to increase IO perf with huge amount of files -* #1141 Clearer backend order list. Added more infos like payment method. +* #1141 Cleaner backend order list. Added more infos like payment method. +* OuputCache: Simple product changes that affect visibility now correctly invalidate all assigned category and manufacturer pages +* * OuputCache: When MegaSearch is active, invalidation occurs only during indexing and not ad-hoc anymore. * #1248 New payment integration guidelines for Sofort\Klarna * TwitterAuth: better error handling and enhanced admin instruction * #1181 Debitoor: Add option to display shipping address on invoices @@ -52,6 +179,12 @@ * (Perf) Many improvements in hooking framework * #1294 Swiss PostFinance: External payment page too small on mobile devices. Added setting for mobile device template URL, pre-configured with PostFinance template. * #1143 Make shipping methods suitable for multi-stores +* #1320 Image import: Find out the content type of image URLs by response header rather than file extension (which is sometimes missing) +* #1219 Recently viewed products list should respect setting to hide manufacturer names +* Import and export product quantity step +* Add bundle item information to order messages +* #1031 Enable offline payment methods to have brand icons +* DevTools Plugin: Added example for cached output invalidation ### Bugfixes * #1268 Data importer always inserts new pictures and does not detect equal pictures while importing @@ -71,7 +204,9 @@ * The tax value per tax rate was not updated when adding\removing a product to\from the order. * The option to send manually was ignored when sending e-mails * #528 LimitedToStores is required on payment provider rather than plugin level - +* #1318 Disabled preselected attribute combination permanently hides the shopping cart button, even if another combination is selected. +* Copy product: Fixes "Cannot insert duplicate key row in object dbo.UrlRecord with unique index IX_UrlRecord_Slug" +* Fixed export publishing via email ## SmartStore.NET 3.0.3 ### Bugfixes @@ -138,7 +273,9 @@ * MegaSearch: Localized labels of filters were never displayed * #1195 Exporter: don't send an email if no email account has been selected * Product lists sometimes show the wrong delivery time -* #1192 Lucene indexing performance decreases the longer it takes +* #1192 Lucene indexing +* +* decreases the longer it takes * #1198 MegaSearch: never sort numeric range by label, always by value * Filter for attributes were always sorted by hit count * #1200 PayPal PLUS: Invalid request if the order amount is zero @@ -172,7 +309,7 @@ * Added config setting *sm:PdfEngineBaseUrl*. There are cases where the PDF converter exits with a network error, when it is unable to load automatically resolved URLs. * (Dev) Added *Retry* utility class * #1176 Admin > Product Search: It ain't possible to search for parts of a product name - + ### Bugfixes * #1145: Fixed HTTP 404 after switching language * Fixed null reference exception in product lists if sorting is not allowed diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 63fae0f3e7..d9e092d65c 100644 --- a/src/AssemblyVersionInfo.cs +++ b/src/AssemblyVersionInfo.cs @@ -9,7 +9,7 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("3.0.3.0")] +[assembly: AssemblyVersion("3.1.5.0")] -[assembly: AssemblyFileVersion("3.0.3.0")] -[assembly: AssemblyInformationalVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.1.5.0")] +[assembly: AssemblyInformationalVersion("3.1.5.0")] diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs index c649970d6d..b591189f8d 100644 --- a/src/Libraries/SmartStore.Core/BaseEntity.cs +++ b/src/Libraries/SmartStore.Core/BaseEntity.cs @@ -6,8 +6,7 @@ using System.Runtime.Serialization; namespace SmartStore.Core -{ - +{ /// /// Base class for entities /// diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs index ec6fc43daf..9629fee3c5 100644 --- a/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/DisplayControl.cs @@ -1,57 +1,185 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Autofac; using SmartStore.Core.Data; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Topics; using SmartStore.Utilities; namespace SmartStore.Core.Caching { + public delegate IEnumerable DisplayControlHandler(BaseEntity entity, IComponentContext ctx); + public partial class DisplayControl : IDisplayControl { - public static readonly HashSet CandidateTypes = new HashSet(new Type[] + private static readonly ConcurrentDictionary _handlers = new ConcurrentDictionary { - typeof(BlogComment), - typeof(BlogPost), - typeof(Category), - typeof(Manufacturer), - typeof(Product), - typeof(ProductBundleItem), - typeof(ProductPicture), - typeof(SpecificationAttribute), - typeof(ProductSpecificationAttribute), - typeof(SpecificationAttributeOption), - typeof(ProductVariantAttribute), - typeof(ProductVariantAttributeValue), - typeof(ProductVariantAttributeCombination), - typeof(TierPrice), - typeof(Discount), - typeof(CrossSellProduct), - typeof(RelatedProduct), - typeof(ProductCategory), - typeof(ProductManufacturer), - typeof(NewsItem), - typeof(NewsComment), - typeof(Topic) - }); + [typeof(BlogComment)] = (x, c) => new[] { "b" + ((BlogComment)x).BlogPostId }, + [typeof(BlogPost)] = (x, c) => new[] { "b" + x.Id }, + [typeof(Category)] = (x, c) => new[] { "c" + x.Id }, + [typeof(Manufacturer)] = (x, c) => new[] { "m" + x.Id }, + [typeof(ProductBundleItem)] = (x, c) => new[] { "p" + ((ProductBundleItem)x).ProductId }, + [typeof(ProductPicture)] = (x, c) => new[] { "p" + ((ProductPicture)x).ProductId }, + [typeof(ProductSpecificationAttribute)] = (x, c) => new[] { "p" + ((ProductSpecificationAttribute)x).ProductId }, + [typeof(ProductVariantAttributeCombination)]= (x, c) => new[] { "p" + ((ProductVariantAttributeCombination)x).ProductId }, + [typeof(TierPrice)] = (x, c) => new[] { "p" + ((TierPrice)x).ProductId }, + [typeof(CrossSellProduct)] = (x, c) => new[] { "p" + ((CrossSellProduct)x).ProductId1, "p" + ((CrossSellProduct)x).ProductId2 }, + [typeof(RelatedProduct)] = (x, c) => new[] { "p" + ((RelatedProduct)x).ProductId1, "p" + ((RelatedProduct)x).ProductId2 }, + [typeof(ProductCategory)] = (x, c) => new[] { "p" + ((ProductCategory)x).CategoryId, "p" + ((ProductCategory)x).ProductId }, + [typeof(ProductManufacturer)] = (x, c) => new[] { "p" + ((ProductManufacturer)x).ManufacturerId, "p" + ((ProductManufacturer)x).ProductId }, + [typeof(NewsItem)] = (x, c) => new[] { "n" + x.Id }, + [typeof(NewsComment)] = (x, c) => new[] { "n" + ((NewsComment)x).NewsItemId }, + [typeof(Topic)] = (x, c) => new[] { "t" + x.Id }, + [typeof(SpecificationAttributeOption)] = (x, c) => ((SpecificationAttributeOption)x).ProductSpecificationAttributes.Select(y => "p" + y.ProductId), + [typeof(ProductTag)] = (x, c) => ((ProductTag)x).Products.Select(y => "p" + y.Id), + [typeof(Product)] = HandleProduct, + [typeof(SpecificationAttribute)] = HandleSpecificationAttribute, + [typeof(ProductVariantAttributeValue)] = HandleProductVariantAttributeValue, + [typeof(Discount)] = HandleDiscount, + [typeof(LocalizedProperty)] = HandleLocalizedProperty + }; private readonly HashSet _entities = new HashSet(); - private readonly Lazy> _rsProductSpecAttr; + private readonly IComponentContext _componentContext; private bool _isIdle; private bool? _isUncacheableRequest; - public DisplayControl(Lazy> rsProductSpecAttr) + public DisplayControl(IComponentContext componentContext) + { + _componentContext = componentContext; + } + + #region Static + + public static bool ContainsHandlerFor(Type type) + { + Guard.NotNull(type, nameof(type)); + + return _handlers.ContainsKey(type); + } + + public static void RegisterHandlerFor(Type type, DisplayControlHandler handler) + { + Guard.NotNull(type, nameof(type)); + Guard.NotNull(handler, nameof(handler)); + + _handlers.TryAdd(type, handler); + } + + #endregion + + #region Handlers + + private static IEnumerable HandleProduct(BaseEntity entity, IComponentContext ctx) + { + var product = ((Product)entity); + yield return "p" + entity.Id; + if (product.ProductType == ProductType.GroupedProduct && product.ParentGroupedProductId > 0) + { + yield return "p" + product.ParentGroupedProductId; + } + } + + private static IEnumerable HandleSpecificationAttribute(BaseEntity entity, IComponentContext ctx) + { + // Determine all affected products (which are assigned to this attribute). + var specAttrId = ((SpecificationAttribute)entity).Id; + var affectedProductIds = ctx.Resolve().Set().AsNoTracking() + .Where(x => x.SpecificationAttributeOption.SpecificationAttribute.Id == specAttrId) + .Select(x => x.ProductId) + .Distinct() + .ToList(); + + foreach (var id in affectedProductIds) + { + yield return "p" + id; + } + } + + private static IEnumerable HandleProductVariantAttributeValue(BaseEntity entity, IComponentContext ctx) { - _rsProductSpecAttr = rsProductSpecAttr; + var pva = ((ProductVariantAttributeValue)entity).ProductVariantAttribute; + if (pva != null) + { + yield return "p" + pva.ProductId; + } + } + + private static IEnumerable HandleDiscount(BaseEntity entity, IComponentContext ctx) + { + var discount = (Discount)entity; + if (discount.DiscountType == DiscountType.AssignedToCategories) + { + foreach (var category in discount.AppliedToCategories) + { + yield return "c" + category.Id; + } + } + else if (discount.DiscountType == DiscountType.AssignedToSkus) + { + foreach (var product in discount.AppliedToProducts) + { + yield return "p" + product.Id; + } + } } + private static IEnumerable HandleLocalizedProperty(BaseEntity entity, IComponentContext ctx) + { + var lp = (LocalizedProperty)entity; + string prefix = null; + BaseEntity targetEntity = null; + + var dbContext = ctx.Resolve(); + + switch (lp.LocaleKeyGroup) + { + case nameof(Product): + prefix = "p"; + break; + case nameof(Category): + prefix = "c"; + break; + case nameof(Manufacturer): + prefix = "m"; + break; + case nameof(Topic): + prefix = "t"; + break; + case nameof(SpecificationAttribute): + targetEntity = dbContext.Set().Find(lp.EntityId); + break; + case nameof(SpecificationAttributeOption): + targetEntity = dbContext.Set().Find(lp.EntityId); + break; + case nameof(ProductVariantAttributeValue): + targetEntity = dbContext.Set().Find(lp.EntityId); + break; + } + + if (prefix.HasValue()) + { + yield return prefix + lp.EntityId; + } + else if (targetEntity != null) + { + var tags = ctx.Resolve().GetCacheControlTagsFor(targetEntity); + foreach (var tag in tags) + { + yield return tag; + } + } + } + + #endregion + public IDisposable BeginIdleScope() { _isIdle = true; @@ -91,157 +219,21 @@ public bool IsUncacheableRequest public virtual IEnumerable GetCacheControlTagsFor(BaseEntity entity) { - Guard.NotNull(entity, nameof(entity)); + var empty = Enumerable.Empty(); - if (entity.IsTransientRecord()) + if (entity == null || entity.IsTransientRecord()) { - yield break; + return empty; } var type = entity.GetUnproxiedType(); - if (!CandidateTypes.Contains(type)) + if (!_handlers.TryGetValue(type, out var handler)) { - yield break; + return empty; } - if (type == typeof(BlogComment)) - { - yield return "b" + ((BlogComment)entity).BlogPostId; - } - else if (type == typeof(BlogPost)) - { - yield return "b" + entity.Id; - } - else if (type == typeof(Category)) - { - yield return "c" + entity.Id; - } - else if (type == typeof(Manufacturer)) - { - yield return "m" + entity.Id; - } - else if (type == typeof(Product)) - { - var product = ((Product)entity); - yield return "p" + entity.Id; - if (product.ProductType == ProductType.GroupedProduct && product.ParentGroupedProductId > 0) - { - yield return "p" + product.ParentGroupedProductId; - } - } - else if (type == typeof(ProductTag)) - { - var ids = ((ProductTag)entity).Products.Select(x => x.Id); - foreach (var id in ids) - { - yield return "p" + id; - } - } - else if (type == typeof(ProductBundleItem)) - { - yield return "p" + ((ProductBundleItem)entity).ProductId; - } - else if (type == typeof(ProductPicture)) - { - yield return "p" + ((ProductPicture)entity).ProductId; - } - else if (type == typeof(SpecificationAttribute)) - { - // Determine all affected products (which are assigned to this attribute). - var specAttrId = ((SpecificationAttribute)entity).Id; - var affectedProductIds = _rsProductSpecAttr.Value.TableUntracked - .Where(x => x.SpecificationAttributeOption.SpecificationAttribute.Id == specAttrId) - .Select(x => x.ProductId) - .Distinct() - .ToList(); - - foreach (var id in affectedProductIds) - { - yield return "p" + id; - } - } - else if (type == typeof(ProductSpecificationAttribute)) - { - yield return "p" + ((ProductSpecificationAttribute)entity).ProductId; - } - else if (type == typeof(SpecificationAttributeOption)) - { - foreach (var attr in ((SpecificationAttributeOption)entity).ProductSpecificationAttributes) - { - yield return "p" + attr.ProductId; - } - } - else if (type == typeof(ProductVariantAttribute)) - { - yield return "p" + ((ProductVariantAttribute)entity).ProductId; - } - else if (type == typeof(ProductVariantAttributeValue)) - { - var pva = ((ProductVariantAttributeValue)entity).ProductVariantAttribute; - if (pva != null) - { - yield return "p" + pva.ProductId; - } - } - else if (type == typeof(ProductVariantAttributeCombination)) - { - yield return "p" + ((ProductVariantAttributeCombination)entity).ProductId; - } - else if (type == typeof(TierPrice)) - { - yield return "p" + ((TierPrice)entity).ProductId; - } - else if (type == typeof(Discount)) - { - var discount = (Discount)entity; - if (discount.DiscountType == DiscountType.AssignedToCategories) - { - foreach (var category in discount.AppliedToCategories) - { - yield return "c" + category.Id; - } - } - else if (discount.DiscountType == DiscountType.AssignedToSkus) - { - foreach (var product in discount.AppliedToProducts) - { - yield return "p" + product.Id; - } - } - } - else if (type == typeof(CrossSellProduct)) - { - yield return "p" + ((CrossSellProduct)entity).ProductId1; - yield return "p" + ((CrossSellProduct)entity).ProductId2; - } - else if (type == typeof(RelatedProduct)) - { - yield return "p" + ((RelatedProduct)entity).ProductId1; - yield return "p" + ((RelatedProduct)entity).ProductId2; - } - else if (type == typeof(ProductCategory)) - { - yield return "c" + ((ProductCategory)entity).CategoryId; - yield return "p" + ((ProductCategory)entity).ProductId; - } - else if (type == typeof(ProductManufacturer)) - { - yield return "m" + ((ProductManufacturer)entity).ManufacturerId; - yield return "p" + ((ProductManufacturer)entity).ProductId; - } - else if (type == typeof(NewsItem)) - { - yield return "n" + entity.Id; - } - else if (type == typeof(NewsComment)) - { - yield return "n" + ((NewsComment)entity).NewsItemId; - } - else if (type == typeof(Topic)) - { - yield return "t" + entity.Id; - } + return handler.Invoke(entity, _componentContext); } public IEnumerable GetAllCacheControlTags() diff --git a/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs index 6542f2a4f6..234b67ced2 100644 --- a/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs +++ b/src/Libraries/SmartStore.Core/Caching/OutputCache/IOutputCacheInvalidationObserver.cs @@ -1,11 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Autofac; using SmartStore.Core.Configuration; -using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Utilities; @@ -14,8 +12,9 @@ namespace SmartStore.Core.Caching public class ObserveEntityContext { public IOutputCacheProvider OutputCacheProvider { get; set; } + public IDisplayControl DisplayControl { get; set; } public BaseEntity Entity { get; set; } - public EntityState EntityState { get; set; } + public IHookedEntity EntityEntry { get; set; } public bool Handled { get; set; } public ContainerManager ServiceContainer { get; set; } } diff --git a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs index d9e6c700b6..0e02723ec4 100644 --- a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs +++ b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; -using System.Linq.Expressions; using Newtonsoft.Json; namespace SmartStore.Collections diff --git a/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs index 6d81d0bc3a..114c4e24bf 100644 --- a/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs +++ b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs @@ -42,7 +42,7 @@ public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize) public MostRecentlyUsedList(string collection, T newItem, int maxSize) { _maxSize = maxSize; - _mru = collection.SplitSafe(Delimiter).Cast().ToList(); + _mru = collection.SplitSafe(Delimiter).Cast().Distinct().ToList(); Add(newItem); } diff --git a/src/Libraries/SmartStore.Core/Collections/ReferenceEqualityComparer.cs b/src/Libraries/SmartStore.Core/Collections/ReferenceEqualityComparer.cs new file mode 100644 index 0000000000..b70b4302d2 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/ReferenceEqualityComparer.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace SmartStore.ComponentModel +{ + public sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer + { + public static ReferenceEqualityComparer Default { get; } = new ReferenceEqualityComparer(); + + public new bool Equals(object x, object y) => ReferenceEquals(x, y); + public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); + } +} diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNode.cs b/src/Libraries/SmartStore.Core/Collections/TreeNode.cs index e9710fac8c..19cf141de9 100644 --- a/src/Libraries/SmartStore.Core/Collections/TreeNode.cs +++ b/src/Libraries/SmartStore.Core/Collections/TreeNode.cs @@ -172,7 +172,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist reader.Read(); objChildren = serializer.Deserialize(reader, sequenceType); } - if (string.Equals(a, "Id", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(a, "Id", StringComparison.OrdinalIgnoreCase)) { reader.Read(); id = serializer.Deserialize(reader); @@ -196,14 +196,15 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist { var metadataProp = FastProperty.GetProperty(objectType, "Metadata", PropertyCachingStrategy.Cached); metadataProp.SetValue(treeNode, metadata); + } - if (id.HasValue()) - { - var idProp = FastProperty.GetProperty(objectType, "Id", PropertyCachingStrategy.Cached); - idProp.SetValue(treeNode, id); - } + // Set Id + if (id.HasValue()) + { + var idProp = FastProperty.GetProperty(objectType, "Id", PropertyCachingStrategy.Cached); + idProp.SetValue(treeNode, id); } - + return treeNode; } diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs index 695866869a..8dd7bee9b2 100644 --- a/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs +++ b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs @@ -34,6 +34,8 @@ public object Id } set { + _id = value; + if (_parent != null) { var map = GetIdNodeMap(); @@ -52,8 +54,6 @@ public object Id map[value] = this; } } - - _id = value; } } @@ -203,20 +203,13 @@ private void AttachTo(T newParent, int? index) { Guard.NotNull(newParent, nameof(newParent)); + var prevParent = _parent; + if (_parent != null) { // Detach from parent _parent.Remove((T)this); } - else - { - // Is a root node with a map: get rid of it. - if (_idNodeMap != null) - { - _idNodeMap.Clear(); - _idNodeMap = null; - } - } if (index == null) { @@ -232,12 +225,70 @@ private void AttachTo(T newParent, int? index) _parent = newParent; - // Set id in new id-node map - if (_id != null) + FixIdNodeMap(prevParent, newParent); + } + + /// + /// Responsible for propagating node ids when detaching/attaching nodes + /// + private void FixIdNodeMap(T prevParent, T newParent) + { + ICollection> keyedNodes = null; + + if (prevParent != null) { - var map = GetIdNodeMap(); - if (map != null) + // A node is moved. We need to detach first. + keyedNodes = new List>(); + + // Detach ids from prev map + var prevMap = prevParent.GetIdNodeMap(); + + Traverse(x => { + // Collect all child node's ids + if (x._id != null) + { + keyedNodes.Add(x); + if (prevMap.ContainsKey(x._id)) + { + // Remove from map + prevMap.Remove(x._id); + } + } + }, true); + } + + if (keyedNodes == null && _idNodeMap != null) + { + // An orphan/root node is attached + keyedNodes = _idNodeMap.Values; + } + + if (newParent != null) + { + // Get new *root map + var map = newParent.GetIdNodeMap(); + + // Merge *this map with *root map + if (keyedNodes != null) + { + foreach (var node in keyedNodes) + { + map[node._id] = node; + } + + // Get rid of *this map after memorizing keyed nodes + if (_idNodeMap != null) + { + _idNodeMap.Clear(); + _idNodeMap = null; + } + } + + if (prevParent == null && _id != null) + { + // When *this was a root, but is keyed, then *this id + // was most likely missing in the prev id-node-map. map[_id] = (T)this; } } @@ -582,7 +633,7 @@ public T SelectNode(Expression> predicate, bool includeSelf = fals } /// - /// Selects all nodes (recursively) with match the given predicate + /// Selects all nodes (recursively) witch match the given predicate /// /// The predicate to match against /// A readonly collection of node matches @@ -613,15 +664,7 @@ public void Remove(T node) var list = node._parent?._children; if (list.Remove(node)) { - // Remove id from id node map - if (node._id != null) - { - var map = node.GetIdNodeMap(); - if (map != null && map.ContainsKey(node._id)) - { - map.Remove(node._id); - } - } + node.FixIdNodeMap(node._parent, null); FixIndexes(list, node._index, -1); @@ -640,11 +683,7 @@ public void Clear() _children.Clear(); } - var map = GetIdNodeMap(); - if (map != null) - { - map.Clear(); - } + FixIdNodeMap(_parent, null); } public void Traverse(Action action, bool includeSelf = false) diff --git a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index 010ef62aae..17d44dbbac 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -234,7 +234,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value) protected virtual bool TrySetMemberCore(string name, object value) { - // first check to see if there's a native property to set + // first check to see if there's a dictionary entry to set if (Properties.ContainsKey(name)) { Properties[name] = value; diff --git a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs index d0ed7563d2..1e467280a9 100644 --- a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs +++ b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs @@ -4,9 +4,7 @@ using System.Text; namespace SmartStore.Core.Configuration -{ - - // codehint: sm-add +{ /// /// Marker attribute. Indicates that the settings should /// be persisted as a JSON string rather than splitted @@ -16,5 +14,4 @@ namespace SmartStore.Core.Configuration public class JsonPersistAttribute : Attribute { } - } diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs index 7ee2b43a39..0d56838cce 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -107,6 +107,12 @@ public static void LoadCollection( var entry = dbContext.Entry(entity); var collection = entry.Collection(navigationProperty); + // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... + if (entry.State == System.Data.Entity.EntityState.Detached) + { + ctx.Attach(entity); + } + if (force) { collection.IsLoaded = false; @@ -156,6 +162,12 @@ public static void LoadReference( var entry = dbContext.Entry(entity); var reference = entry.Reference(navigationProperty); + // Avoid System.InvalidOperationException: Member 'IsLoaded' cannot be called for property... + if (entry.State == System.Data.Entity.EntityState.Detached) + { + ctx.Attach(entity); + } + if (force) { reference.IsLoaded = false; diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index 2ced0c35c9..0be6f2d778 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -25,7 +25,7 @@ public CatalogSettings() { FileUploadAllowedExtensions = new List(); AllowProductSorting = true; - DefaultSortOrder = ProductSortingEnum.Initial; + DefaultSortOrder = ProductSortingEnum.Relevance; AllowProductViewModeChanging = true; DefaultViewMode = "grid"; CategoryBreadcrumbEnabled = true; @@ -80,6 +80,8 @@ public CatalogSettings() ShowProductsFromSubcategories = true; ApplyTierPricePercentageToAttributePriceAdjustments = false; AllowDifferingEmailAddressForEmailAFriend = false; + AllowAnonymousUsersToEmailAFriend = false; + AllowAnonymousUsersToReviewProduct = false; } /// @@ -399,15 +401,25 @@ public CatalogSettings() public bool ShowDiscountSign { get; set; } - /// - /// Gets or sets the available customer selectable default page size options - /// - public string DefaultPageSizeOptions { get; set; } + /// + /// Gets or sets the price display style for prices + /// + public PriceDisplayStyle PriceDisplayStyle { get; set; } - /// - /// Gets or sets the price display type for prices in product lists - /// - public PriceDisplayType PriceDisplayType { get; set; } + /// + /// Displays a textual resources instead of the decimal value when prices are 0 + /// + public bool DisplayTextForZeroPrices { get; set; } + + /// + /// Gets or sets the available customer selectable default page size options + /// + public string DefaultPageSizeOptions { get; set; } + + /// + /// Gets or sets the price display type for prices in product lists + /// + public PriceDisplayType PriceDisplayType { get; set; } /// /// Gets or sets a value indicating whether to include "Short description" in compare products diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs index bbe46d5043..5a895b03a8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs @@ -124,7 +124,6 @@ public partial class Category : BaseEntity, ICategoryNode, IAuditable, ISoftDele /// /// Gets or sets the available price ranges /// - [DataMember] [Obsolete("Price ranges are calculated automatically since version 3")] [StringLength(400)] public string PriceRanges { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs index 844335b79b..823f5e57fb 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Manufacturer.cs @@ -88,7 +88,6 @@ public partial class Manufacturer : BaseEntity, IAuditable, ISoftDeletable, ILoc /// /// Gets or sets the available price ranges /// - [DataMember] [Obsolete("Price ranges are calculated automatically since version 3")] [StringLength(400)] public string PriceRanges { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs new file mode 100644 index 0000000000..adee8aa61f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/PriceDisplayStyle.cs @@ -0,0 +1,26 @@ +using System; + +namespace SmartStore.Core.Domain.Catalog +{ + /// + /// Represents the style in which prices are displayed + /// + [Flags] + public enum PriceDisplayStyle + { + /// + /// Display prices without badges + /// + Default = 1, + + /// + /// Display all prices within badges + /// + BadgeAll = 2, + + /// + /// Display prices of free products within badges + /// + BadgeFreeProductsOnly = 4 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index 45d8f6b372..a4bc4c5251 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; +using System.Linq.Expressions; using System.Runtime.Serialization; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; @@ -19,7 +20,41 @@ namespace SmartStore.Core.Domain.Catalog [DataContract] public partial class Product : BaseEntity, IAuditable, ISoftDeletable, ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported, IMergedData { - private ICollection _productCategories; + #region static + + private static readonly HashSet _visibilityAffectingProductProps = new HashSet(); + + static Product() + { + AddPropsToSet(_visibilityAffectingProductProps, + x => x.AvailableEndDateTimeUtc, + x => x.AvailableStartDateTimeUtc, + x => x.Deleted, + x => x.LowStockActivityId, + x => x.LimitedToStores, + x => x.ManageInventoryMethodId, + x => x.MinStockQuantity, + x => x.Published, + x => x.SubjectToAcl, + x => x.VisibleIndividually); + } + + static void AddPropsToSet(HashSet props, params Expression>[] lambdas) + { + foreach (var lambda in lambdas) + { + props.Add(lambda.ExtractPropertyInfo().Name); + } + } + + public static HashSet GetVisibilityAffectingPropertyNames() + { + return _visibilityAffectingProductProps; + } + + #endregion + + private ICollection _productCategories; private ICollection _productManufacturers; private ICollection _productPictures; private ICollection _productReviews; @@ -30,8 +65,8 @@ public partial class Product : BaseEntity, IAuditable, ISoftDeletable, ILocalize private ICollection _tierPrices; private ICollection _appliedDiscounts; private ICollection _productBundleItems; - - private int _stockQuantity; + + private int _stockQuantity; private int _backorderModeId; private string _sku; private string _gtin; @@ -262,8 +297,8 @@ public string Gtin /// /// Gets or sets the download identifier /// - [DataMember] - public int DownloadId { get; set; } + [Obsolete("Since version 3.2 more than one download can be assigned to a product. See property Download.EntityId and Download.EntityName.")] + public int DownloadId { get; set; } /// /// Gets or sets a value indicating whether this downloadable product can be downloaded unlimited number of times @@ -694,9 +729,23 @@ public decimal Height [Index] public bool Deleted { get; set; } - /// - /// Gets or sets the date and time of product creation - /// + /// + /// Gets or sets a value indicating whether the entity is a system product. + /// + [DataMember] + [Index("Product_SystemName_IsSystemProduct", 2)] + public bool IsSystemProduct { get; set; } + + /// + /// Gets or sets the product system name. + /// + [DataMember] + [Index("Product_SystemName_IsSystemProduct", 1)] + public string SystemName { get; set; } + + /// + /// Gets or sets the date and time of product creation + /// [DataMember] public DateTime CreatedOnUtc { get; set; } @@ -851,10 +900,16 @@ public bool BasePriceHasValue [DataMember] public int? MainPictureId { get; set; } - /// - /// Gets or sets the product type + /// + /// Gets or sets a value that indictaes whether the product has a preview picture /// [DataMember] + public bool HasPreviewPicture { get; set; } + + /// + /// Gets or sets the product type + /// + [DataMember] public ProductType ProductType { get @@ -1089,5 +1144,5 @@ public virtual ICollection ProductBundleItems get { return _productBundleItems ?? (_productBundleItems = new HashSet()); } protected set { _productBundleItems = value; } } - } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs index 8a8a5bde7a..72742f23b3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductAttribute.cs @@ -58,10 +58,16 @@ public partial class ProductAttribute : BaseEntity, ILocalizedEntity, ISearchAli [DataMember] public bool IndexOptionNames { get; set; } - /// - /// Gets or sets the prooduct attribute option sets - /// - [DataMember] + /// + /// Gets or sets export mappings. + /// + [DataMember] + public string ExportMappings { get; set; } + + /// + /// Gets or sets the prooduct attribute option sets + /// + [DataMember] public virtual ICollection ProductAttributeOptionsSets { get { return _productAttributeOptionsSets ?? (_productAttributeOptionsSets = new HashSet()); } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs index dd88e87c65..1440fd556f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSortingEnum.cs @@ -1,7 +1,7 @@ namespace SmartStore.Core.Domain.Catalog { /// - /// Represents the product sorting + /// Represents the product sorting. /// public enum ProductSortingEnum { @@ -9,30 +9,38 @@ public enum ProductSortingEnum /// Initial state /// Initial = 0, + /// /// Relevance /// Relevance = 1, + /// /// Name: A to Z /// NameAsc = 5, + /// /// Name: Z to A /// NameDesc = 6, + /// /// Price: Low to High /// PriceAsc = 10, + /// /// Price: High to Low /// PriceDesc = 11, + /// - /// Product creation date + /// Product creation date. + /// Actually CreatedOnDesc (but due to localization this remains as is). /// - CreatedOn = 15, // eigentlich CreatedOnDesc (wegen Lokalisierung bleibt das aber so) + CreatedOn = 15, + /// /// Product creation date /// diff --git a/src/Libraries/SmartStore.Core/Domain/Common/AddressSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/AddressSettings.cs index 9fa5d46521..6d39656c46 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/AddressSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/AddressSettings.cs @@ -19,7 +19,9 @@ public AddressSettings() CityEnabled = true; CityRequired = true; CountryEnabled = true; + CountryRequired = true; StateProvinceEnabled = true; + StateProvinceRequired = false; PhoneEnabled = true; PhoneRequired = true; FaxEnabled = true; @@ -95,15 +97,25 @@ public AddressSettings() /// public bool CountryEnabled { get; set; } - /// - /// Gets or sets a value indicating whether 'State / province' is enabled - /// - public bool StateProvinceEnabled { get; set; } + /// + /// Gets or sets a value indicating whether 'Country' is required + /// + public bool CountryRequired { get; set; } - /// - /// Gets or sets a value indicating whether 'Phone number' is enabled - /// - public bool PhoneEnabled { get; set; } + /// + /// Gets or sets a value indicating whether 'State / province' is enabled + /// + public bool StateProvinceEnabled { get; set; } + + /// + /// Gets or sets a value indicating whether 'State / province' is required + /// + public bool StateProvinceRequired { get; set; } + + /// + /// Gets or sets a value indicating whether 'Phone number' is enabled + /// + public bool PhoneEnabled { get; set; } /// /// Gets or sets a value indicating whether 'Phone number' is required /// diff --git a/src/Libraries/SmartStore.Core/Domain/Common/AdminAreaSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/AdminAreaSettings.cs index 2458d9faa7..466c1bbe79 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/AdminAreaSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/AdminAreaSettings.cs @@ -9,13 +9,10 @@ public AdminAreaSettings() { GridPageSize = 25; DisplayProductPictures = true; - RichEditorFlavor = "RichEditor"; } public int GridPageSize { get; set; } public bool DisplayProductPictures { get; set; } - - public string RichEditorFlavor { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs index b67b6d3b30..46d94f8c98 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -9,7 +9,9 @@ public CommonSettings() UseStoredProceduresIfSupported = true; AutoUpdateEnabled = true; EntityPickerPageSize = 48; - } + MaxScheduleHistoryAgeInDays = 30; + MaxNumberOfScheduleHistoryEntries = 100; + } public bool UseSystemEmailForContactUsForm { get; set; } @@ -28,5 +30,15 @@ public CommonSettings() /// Gets or sets the page size for the entity picker /// public int EntityPickerPageSize { get; set; } - } + + /// + /// Gets or sets the maximum age of schedule history entries (in days). + /// + public int MaxScheduleHistoryAgeInDays { get; set; } + + /// + /// Gets or sets the maximum number of schedule history entries per task. + /// + public int MaxNumberOfScheduleHistoryEntries { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs b/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs index c4fa6cc228..31668ab4ed 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/Customer.cs @@ -21,7 +21,8 @@ public partial class Customer : BaseEntity, ISoftDeletable private ICollection _shoppingCartItems; private ICollection _orders; private ICollection _rewardPointsHistory; - private ICollection _returnRequests; + private ICollection _walletHistory; + private ICollection _returnRequests; private ICollection
_addresses; private ICollection _forumTopics; private ICollection _forumPosts; @@ -143,13 +144,39 @@ public PasswordFormat PasswordFormat ///
[DataMember, Index("IX_Customer_LastActivity")] public DateTime LastActivityDateUtc { get; set; } - - #region Navigation properties - /// - /// Gets or sets customer generated content - /// - public virtual ICollection ExternalAuthenticationRecords + /// + /// For future use + /// + public string Salutation { get; set; } + + [DataMember] + public string Title { get; set; } + + [DataMember] + public string FirstName { get; set; } + + [DataMember] + public string LastName { get; set; } + + [DataMember, Index("IX_Customer_FullName")] + public string FullName { get; set; } + + [DataMember, Index("IX_Customer_Company")] + public string Company { get; set; } + + [DataMember, Index("IX_Customer_CustomerNumber")] + public string CustomerNumber { get; set; } + + [DataMember, Index("IX_Customer_BirthDate")] + public DateTime? BirthDate { get; set; } + + #region Navigation properties + + /// + /// Gets or sets customer generated content + /// + public virtual ICollection ExternalAuthenticationRecords { get { return _externalAuthenticationRecords ?? (_externalAuthenticationRecords = new HashSet()); } protected set { _externalAuthenticationRecords = value; } @@ -202,6 +229,21 @@ public virtual ICollection RewardPointsHistory protected set { _rewardPointsHistory = value; } } + /// + /// Gets or sets the wallet history. + /// + public virtual ICollection WalletHistory + { + get + { + return _walletHistory ?? (_walletHistory = new HashSet()); + } + protected set + { + _walletHistory = value; + } + } + /// /// Gets or sets return request of this customer /// diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs index f333486399..3e038689f7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerNumberMethod.cs @@ -6,17 +6,17 @@ namespace SmartStore.Core.Domain.Customers public enum CustomerNumberMethod { /// - /// no customer number will be saved + /// No customer number will be saved /// Disabled = 10, /// - /// customer numbers can be saved + /// Customer numbers can be saved /// Enabled = 20, /// - /// customer numbers will automatically be set when new customers are created + /// Customer numbers will automatically be set when new customers are created /// AutomaticallySet = 30, diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index c14bbfa1a3..74beb552c2 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -7,14 +7,14 @@ public class CustomerSettings : ISettings public CustomerSettings() { UsernamesEnabled = true; - CustomerNumberMethod = Customers.CustomerNumberMethod.Disabled; - CustomerNumberVisibility = Customers.CustomerNumberVisibility.None; + CustomerNumberMethod = CustomerNumberMethod.Disabled; + CustomerNumberVisibility = CustomerNumberVisibility.None; DefaultPasswordFormat = PasswordFormat.Hashed; HashedPasswordFormat = "SHA1"; PasswordMinLength = 6; UserRegistrationType = UserRegistrationType.Standard; AvatarMaximumSizeBytes = 512000; - DefaultAvatarEnabled = true; + DefaultAvatarEnabled = false; CustomerNameFormat = CustomerNameFormat.ShowFirstName; CustomerNameFormatMaxLength = 64; GenderEnabled = true; @@ -23,8 +23,8 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = true; - StoreLastIpAddress = true; - DisplayPrivacyAgreementOnContactUs = false; + FirstNameRequired = false; + LastNameRequired = false; } /// @@ -151,16 +151,6 @@ public CustomerSettings() /// Gets or sets a value indicating we should store last visited page URL for each customer /// public bool StoreLastVisitedPage { get; set; } - - /// - /// Gets or sets a value indicating whether to store last IP address for each customer - /// - public bool StoreLastIpAddress { get; set; } - - /// - /// Gets or sets a value indicating whether to display a checkbox to the customer where he can agree to privacy terms - /// - public bool DisplayPrivacyAgreementOnContactUs { get; set; } #region Form fields @@ -174,10 +164,20 @@ public CustomerSettings() ///
public bool TitleEnabled { get; set; } - /// - /// Gets or sets a value indicating whether 'Date of Birth' is enabled - /// - public bool DateOfBirthEnabled { get; set; } + /// + /// Gets or sets a value indicating whether 'FirstName' is required + /// + public bool FirstNameRequired { get; set; } + + /// + /// Gets or sets a value indicating whether 'LastName' is required + /// + public bool LastNameRequired { get; set; } + + /// + /// Gets or sets a value indicating whether 'Date of Birth' is enabled + /// + public bool DateOfBirthEnabled { get; set; } /// /// Gets or sets a value indicating whether 'Company' is enabled diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/PrivacySettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/PrivacySettings.cs new file mode 100644 index 0000000000..98f362aece --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/PrivacySettings.cs @@ -0,0 +1,47 @@ +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Orders; + +namespace SmartStore.Core.Domain.Customers +{ + public class PrivacySettings : BaseEntity, ISettings, ILocalizedEntity + { + public PrivacySettings() + { + EnableCookieConsent = true; + StoreLastIpAddress = false; + DisplayGdprConsentOnForms = true; + FullNameOnContactUsRequired = false; + } + + /// + /// Specifies whether cookie hint and consent will be displayed to customers in the frontent + /// + public bool EnableCookieConsent { get; set; } + + /// + /// Specifies the text which will be display to customers using the frontend + /// + public string CookieConsentBadgetext { get; set; } + + /// + /// Gets or sets a value indicating whether to store last IP address for each customer + /// + public bool StoreLastIpAddress { get; set; } + + /// + /// Gets or sets a value indicating whether to display a checkbox to the customer where he can agree to privacy terms + /// + public bool DisplayGdprConsentOnForms { get; set; } + + /// + /// Gets or sets a value indicating whether the full name field is required on contact us requests + /// + public bool FullNameOnContactUsRequired { get; set; } + + /// + /// Gets or sets a value indicating whether the full name field is required on product requests + /// + public bool FullNameOnProductRequestRequired { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs index e22419138e..525bdaf2b7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerAttributeNames.cs @@ -3,13 +3,8 @@ namespace SmartStore.Core.Domain.Customers { public static partial class SystemCustomerAttributeNames { - //Form fields - public static string Title { get { return "Title"; } } - public static string FirstName { get { return "FirstName"; } } - public static string LastName { get { return "LastName"; } } - public static string Gender { get { return "Gender"; } } - public static string DateOfBirth { get { return "DateOfBirth"; } } - public static string Company { get { return "Company"; } } + // Form fields + public static string Gender { get { return "Gender"; } } public static string StreetAddress { get { return "StreetAddress"; } } public static string StreetAddress2 { get { return "StreetAddress2"; } } public static string ZipPostalCode { get { return "ZipPostalCode"; } } @@ -20,15 +15,16 @@ public static partial class SystemCustomerAttributeNames public static string Fax { get { return "Fax"; } } public static string VatNumber { get { return "VatNumber"; } } public static string VatNumberStatusId { get { return "VatNumberStatusId"; } } - public static string TimeZoneId { get { return "TimeZoneId"; } } - public static string CustomerNumber { get { return "CustomerNumber"; } } + public static string TimeZoneId { get { return "TimeZoneId"; } } - //Other attributes + // Other attributes public static string DiscountCouponCode { get { return "DiscountCouponCode"; } } public static string GiftCardCouponCodes { get { return "GiftCardCouponCodes"; } } public static string CheckoutAttributes { get { return "CheckoutAttributes"; } } public static string AvatarPictureId { get { return "AvatarPictureId"; } } + public static string AvatarColor { get { return "AvatarColor"; } } public static string ForumPostCount { get { return "ForumPostCount"; } } + public static string LastForumVisit { get { return "LastForumVisit"; } } public static string Signature { get { return "Signature"; } } public static string PasswordRecoveryToken { get { return "PasswordRecoveryToken"; } } public static string AccountActivationToken { get { return "AccountActivationToken"; } } @@ -38,8 +34,10 @@ public static partial class SystemCustomerAttributeNames public static string AdminAreaStoreScopeConfiguration { get { return "AdminAreaStoreScopeConfiguration"; } } public static string MostRecentlyUsedCategories { get { return "MostRecentlyUsedCategories"; } } public static string MostRecentlyUsedManufacturers { get { return "MostRecentlyUsedManufacturers"; } } + public static string WalletEnabled { get { return "WalletEnabled"; } } + public static string HasConsentedToGdpr { get { return "HasConsentedToGdpr"; } } - //depends on store + // Depends on store public static string CurrencyId { get { return "CurrencyId"; } } public static string LanguageId { get { return "LanguageId"; } } public static string SelectedPaymentMethod { get { return "SelectedPaymentMethod"; } } @@ -50,5 +48,6 @@ public static partial class SystemCustomerAttributeNames public static string WorkingThemeName { get { return "WorkingThemeName"; } } public static string TaxDisplayTypeId { get { return "TaxDisplayTypeId"; } } public static string UseRewardPointsDuringCheckout { get { return "UseRewardPointsDuringCheckout"; } } - } + public static string UseCreditBalanceDuringCheckout { get { return "UseCreditBalanceDuringCheckout"; } } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/WalletHistory.cs b/src/Libraries/SmartStore.Core/Domain/Customers/WalletHistory.cs new file mode 100644 index 0000000000..d85437c4f7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/WalletHistory.cs @@ -0,0 +1,74 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using SmartStore.Core.Domain.Orders; + +namespace SmartStore.Core.Domain.Customers +{ + /// + /// Represents a digital wallet history entry. + /// + public class WalletHistory : BaseEntity + { + /// + /// Gets or sets the store identifier. Should not be zero. + /// + [Index("IX_StoreId_CreatedOn", 0)] + public int StoreId { get; set; } + + /// + /// Gets or sets the customer identifier. + /// + public int CustomerId { get; set; } + + /// + /// Gets or sets the order identifier. + /// + public int? OrderId { get; set; } + + /// + /// Gets or sets the amount of the entry. + /// + public decimal Amount { get; set; } + + /// + /// Gets or sets the amount balance when the entry was created. + /// + public decimal AmountBalance { get; set; } + + /// + /// Gets or sets the amount balance per store when the entry was created. + /// + public decimal AmountBalancePerStore { get; set; } + + /// + /// Gets or sets the date ehen the entry was created (in UTC). + /// + [Index("IX_StoreId_CreatedOn", 1)] + public DateTime CreatedOnUtc { get; set; } + + /// + /// Gets or sets the reason for posting this entry. + /// + public WalletPostingReason? Reason { get; set; } + + /// + /// Gets or sets the message. + /// + public string Message { get; set; } + + /// + /// Gets or sets the admin comment. + /// + public string AdminComment { get; set; } + + /// + /// Gets or sets the customer. + /// + public virtual Customer Customer { get; set; } + + /// + /// Gets or sets the order for which the wallet entry was used. + /// + public virtual Order Order { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/WalletPostingReason.cs b/src/Libraries/SmartStore.Core/Domain/Customers/WalletPostingReason.cs new file mode 100644 index 0000000000..06a829dba8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/WalletPostingReason.cs @@ -0,0 +1,38 @@ +namespace SmartStore.Core.Domain.Customers +{ + /// + /// Represents the reason for creating a wallet history entry. + /// + public enum WalletPostingReason + { + /// + /// Any administration reason. + /// + Admin = 0, + + /// + /// The Customer has purchased goods which have been paid in part or in full by wallet. + /// + Purchase, + + /// + /// The customer has bought wallet credits. + /// + Refill, + + /// + /// The admin has refunded the used credit balance. + /// + Refund, + + /// + /// The admin has refunded a part of the used credit balance. + /// + PartialRefund, + + /// + /// The admin has debited the wallet, e.g. because the purchase of credit was cancelled. + /// + Debit + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 1b00fe2e27..476c1a5b8c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -170,14 +170,19 @@ public class ExportFilter /// public bool? IsActiveSubscriber { get; set; } - #endregion + /// + /// Filter by language + /// + public int? WorkingLanguageId { get; set; } - #region Shopping Cart + #endregion - /// - /// Filter by shopping cart type identifier - /// - public int? ShoppingCartTypeId { get; set; } + #region Shopping Cart + + /// + /// Filter by shopping cart type identifier + /// + public int? ShoppingCartTypeId { get; set; } #endregion } diff --git a/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs b/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs index c20e16638a..048486e36b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/Currency.cs @@ -1,5 +1,8 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; using System.Runtime.Serialization; +using Newtonsoft.Json; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Stores; @@ -114,6 +117,35 @@ public Currency() [DataMember] public CurrencyRoundingRule RoundOrderTotalRule { get; set; } - #endregion Rounding + #endregion Rounding + + #region Utils + + private NumberFormatInfo _numberFormat; + + [NotMapped, JsonIgnore, IgnoreDataMember] + public NumberFormatInfo NumberFormat + { + get + { + if (_numberFormat == null && DisplayLocale.HasValue()) + { + try + { + _numberFormat = CultureInfo.CreateSpecificCulture(DisplayLocale).NumberFormat; + } + catch { } + } + + if (_numberFormat == null) + { + _numberFormat = NumberFormatInfo.CurrentInfo; + } + + return _numberFormat; + } + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs new file mode 100644 index 0000000000..47fe21281b --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumDateFilter.cs @@ -0,0 +1,14 @@ +namespace SmartStore.Core.Domain.Forums +{ + public enum ForumDateFilter + { + LastVisit = 0, + Yesterday = 1, + LastWeek = 7, + LastTwoWeeks = 14, + LastMonth = 30, + LastThreeMonths = 92, + LastSixMonths = 183, + LastYear = 365 + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs deleted file mode 100644 index 169754cd58..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSearchType.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace SmartStore.Core.Domain.Forums -{ - /// - /// Represents a forum search type - /// - public enum ForumSearchType - { - /// - /// Topic titles and post text - /// - All = 0, - /// - /// Topic titles only - /// - TopicTitlesOnly = 10, - /// - /// Post text only - /// - PostTextOnly = 20, - } -} diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs index c8b28c4197..89406566f7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -13,8 +13,9 @@ public ForumSettings() StrippedTopicMaxLength = 45; TopicsPageSize = 10; PostsPageSize = 10; - SearchResultsPageSize = 10; - LatestCustomerPostsPageSize = 10; + SearchResultsPageSize = 12; + AllowSorting = true; + LatestCustomerPostsPageSize = 10; ShowCustomersPostCount = true; ForumEditor = EditorType.BBCodeEditor; SignaturesEnabled = true; @@ -26,7 +27,6 @@ public ForumSettings() ActiveDiscussionsPageTopicCount = 50; ActiveDiscussionsFeedCount = 25; ForumFeedCount = 10; - ForumSearchTermMinimumLength = 3; } /// @@ -90,14 +90,14 @@ public ForumSettings() public int PostsPageSize { get; set; } /// - /// Gets or sets the number of links to display for pagination of posts in topics + /// Gets or sets the page size for search result /// - public int TopicPostsPageLinkDisplayCount { get; set; } + public int SearchResultsPageSize { get; set; } /// - /// Gets or sets the page size for search result + /// Gets or sets a value indicating whether sorting is enabled. /// - public int SearchResultsPageSize { get; set; } + public bool AllowSorting { get; set; } /// /// Gets or sets the page size for latest customer posts @@ -183,10 +183,5 @@ public ForumSettings() /// Gets or sets the number of items to display for Forum RSS Feed /// public int ForumFeedCount { get; set; } - - /// - /// Gets or sets the minimum length for search term - /// - public int ForumSearchTermMinimumLength { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs index 50a3ab730a..4b30ac9b02 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -38,6 +38,12 @@ public partial class ForumTopic : BaseEntity, IAuditable /// public int Views { get; set; } + /// + /// Gets or sets the first post identifier, for example of the first search hit. + /// This property is not a data member. + /// + public int FirstPostId { get; set; } + /// /// Gets or sets the last post identifier /// diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs new file mode 100644 index 0000000000..014b2ac694 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopicSorting.cs @@ -0,0 +1,58 @@ +namespace SmartStore.Core.Domain.Forums +{ + /// + /// Represents the sorting of forum topics. + /// + public enum ForumTopicSorting + { + /// + /// Initial state + /// + Initial = 0, + + /// + /// Relevance + /// + Relevance, + + /// + /// Subject: A to Z + /// + SubjectAsc, + + /// + /// Subject: Z to A + /// + SubjectDesc, + + /// + /// User name: A to Z + /// + UserNameAsc, + + /// + /// User name: Z to A + /// + UserNameDesc, + + /// + /// Creation date: Oldest first + /// + CreatedOnAsc, + + /// + /// Creation date: Newest first + /// + CreatedOnDesc, + + /// + /// Number of posts: Low to High + /// + PostsAsc, + + /// + /// Number of posts: High to Low + /// + PostsDesc + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs b/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs index 8e1259aff8..1d4b30386e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/Language.cs @@ -22,13 +22,13 @@ public partial class Language : BaseEntity, IStoreMappingSupported public string Name { get; set; } /// - /// Gets or sets the language culture + /// Gets or sets the language culture (e.g. "en-US") /// [DataMember] public string LanguageCulture { get; set; } /// - /// Gets or sets the unique SEO code + /// Gets or sets the unique SEO code (e.g. "en") /// [DataMember] public string UniqueSeoCode { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Media/Download.cs b/src/Libraries/SmartStore.Core/Domain/Media/Download.cs index fcc7cc815f..476c8d4ea3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/Download.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/Download.cs @@ -1,4 +1,7 @@ +using NuGet; +using SmartStore.Core.Domain.Catalog; using System; +using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Runtime.Serialization; @@ -83,5 +86,33 @@ public partial class Download : BaseEntity, ITransient, IHasMedia /// Gets or sets the media storage /// public virtual MediaStorage MediaStorage { get; set; } - } + + /// + /// Gets or sets a value indicating the corresponding entity id + /// + [DataMember] + [Index("IX_EntityId_EntityName", 0)] + public int EntityId { get; set; } + + /// + /// Gets or sets a value indicating the corresponding entity name + /// + [DataMember] + [Index("IX_EntityId_EntityName", 1)] + [StringLength(100)] + public string EntityName { get; set; } + + /// + /// Gets or sets a value the verion info + /// + [DataMember] + [StringLength(30)] + public string FileVersion { get; set; } + + /// + /// Gets or sets a value which contains information about changes of the current download version + /// + [DataMember] + public string Changelog { get; set; } + } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs b/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs index 6827d1a1cc..a10f7193c1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/Events.cs @@ -1,84 +1,64 @@ -using System.Collections.Generic; +using System; namespace SmartStore.Core.Domain.Messages { - public class EmailSubscribedEvent + public class EmailSubscribedEvent : IEquatable { - private readonly string _email; - - public EmailSubscribedEvent(string email) + public EmailSubscribedEvent(string email) { - _email = email; + Email = email; } - public string Email - { - get { return _email; } - } + public string Email { get; private set; } - public bool Equals(EmailSubscribedEvent other) + public override bool Equals(object obj) + { + return Equals(obj as EmailSubscribedEvent); + } + + public bool Equals(EmailSubscribedEvent other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other._email, _email); - } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != typeof(EmailSubscribedEvent)) - return false; - return Equals((EmailSubscribedEvent)obj); + return Equals(other.Email, Email); } public override int GetHashCode() { - return (_email != null ? _email.GetHashCode() : 0); + return (Email != null ? Email.GetHashCode() : 0); } } - public class EmailUnsubscribedEvent - { - private readonly string _email; - - public EmailUnsubscribedEvent(string email) + public class EmailUnsubscribedEvent : IEquatable + { + public EmailUnsubscribedEvent(string email) { - _email = email; + Email = email; } - public string Email - { - get { return _email; } - } + public string Email { get; private set; } - public bool Equals(EmailUnsubscribedEvent other) + public override bool Equals(object obj) + { + return Equals(obj as EmailUnsubscribedEvent); + } + + public bool Equals(EmailUnsubscribedEvent other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Equals(other._email, _email); - } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != typeof(EmailUnsubscribedEvent)) - return false; - return Equals((EmailUnsubscribedEvent)obj); + return Equals(other.Email, Email); } public override int GetHashCode() { - return (_email != null ? _email.GetHashCode() : 0); + return (Email != null ? Email.GetHashCode() : 0); } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplatesSettings.cs b/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplatesSettings.cs deleted file mode 100644 index 43394df849..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Messages/MessageTemplatesSettings.cs +++ /dev/null @@ -1,36 +0,0 @@ -using SmartStore.Core.Configuration; - -namespace SmartStore.Core.Domain.Messages -{ - public class MessageTemplatesSettings : ISettings - { - public MessageTemplatesSettings() - { - Color1 = "#3A87AD"; - Color2 = "#F7F7F7"; - Color3 = "#F5F5F5"; - } - - /// - /// Gets or sets a value indicating whether to replace message tokens according to case invariant rules - /// - public bool CaseInvariantReplacement { get; set; } - - /// - /// Gets or sets a color1 in hex format ("#hhhhhh") to use in workflow message formatting - /// - public string Color1 { get; set; } - - /// - /// Gets or sets a color2 in hex format ("#hhhhhh") to use in workflow message formatting - /// - public string Color2 { get; set; } - - /// - /// Gets or sets a color3 in hex format ("#hhhhhh") to use in workflow message formatting - /// - public string Color3 { get; set; } - - } - -} diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs b/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs index 6339d91d32..ff24400987 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/NewsLetterSubscription.cs @@ -34,5 +34,10 @@ public partial class NewsLetterSubscription : BaseEntity /// [Index("IX_NewsletterSubscription_Email_StoreId", 2)] public int StoreId { get; set; } + + /// + /// Gets or sets the language identifier + /// + public int WorkingLanguageId { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs index 29be2034f6..96d262c76c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/Order.cs @@ -20,8 +20,8 @@ namespace SmartStore.Core.Domain.Orders [DataContract] public partial class Order : BaseEntity, IAuditable, ISoftDeletable { - - private ICollection _discountUsageHistory; + private ICollection _walletHistory; + private ICollection _discountUsageHistory; private ICollection _giftCardUsageHistory; private ICollection _orderNotes; private ICollection _orderItems; @@ -230,10 +230,16 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat [DataMember] public decimal OrderDiscount { get; set; } - /// - /// /// Gets or sets the order total rounding amount - /// - [DataMember] + /// + /// Gets or sets the wallet credit amount used to (partially) pay this order. + /// + [DataMember] + public decimal CreditBalance { get; set; } + + /// + /// Gets or sets the order total rounding amount + /// + [DataMember] public decimal OrderTotalRounding { get; set; } /// @@ -248,10 +254,10 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat [DataMember] public decimal RefundedAmount { get; set; } - /// - /// Gets or sets the value indicating whether reward points were earned for this order - /// - [DataMember] + /// + /// Gets or sets the value indicating whether reward points were earned for this order + /// + [DataMember] public bool RewardPointsWereAdded { get; set; } /// @@ -495,10 +501,19 @@ protected virtual SortedDictionary ParseTaxRates(string taxRat /// public virtual RewardPointsHistory RedeemedRewardPointsEntry { get; set; } - /// - /// Gets or sets discount usage history - /// - public virtual ICollection DiscountUsageHistory + /// + /// Gets or sets the wallet history. + /// + public virtual ICollection WalletHistory + { + get { return _walletHistory ?? (_walletHistory = new HashSet()); } + protected set { _walletHistory = value; } + } + + /// + /// Gets or sets discount usage history + /// + public virtual ICollection DiscountUsageHistory { get { return _discountUsageHistory ?? (_discountUsageHistory = new HashSet()); } protected set { _discountUsageHistory = value; } diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderItem.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderItem.cs index f3f2fc6416..48ca396073 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderItem.cs @@ -128,6 +128,18 @@ public partial class OrderItem : BaseEntity [DataMember] public decimal ProductCost { get; set; } + /// + /// Gets or sets the delivery time at the time of purchase. + /// + [DataMember] + public int? DeliveryTimeId { get; set; } + + /// + /// Indicates whether the delivery time was displayed at the time of purchase. + /// + [DataMember] + public bool DisplayDeliveryTime { get; set; } + /// /// Gets the order /// diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs index 2c6c73b211..888c2ac83e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ReturnRequest.cs @@ -1,12 +1,12 @@ using System; -using SmartStore.Core.Domain.Customers; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Orders { - /// - /// Represents a return request - /// + /// + /// Represents a return request + /// [DataContract] public partial class ReturnRequest : BaseEntity, IAuditable { @@ -75,10 +75,16 @@ public partial class ReturnRequest : BaseEntity, IAuditable /// [DataMember] public int ReturnRequestStatusId { get; set; } - - /// - /// Gets or sets the date and time of entity creation - /// + + /// + /// Gets or sets whether to refund to wallet. + /// + [DataMember] + public bool? RefundToWallet { get; set; } + + /// + /// Gets or sets the date and time of entity creation + /// [DataMember] public DateTime CreatedOnUtc { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs index d1e837b4ec..e569f371b7 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -26,6 +26,7 @@ public ShoppingCartSettings() ShowBasePrice = true; ShowDeliveryTimes = true; ShowShortDesc = true; + AllowAnonymousUsersToEmailWishlist = false; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs b/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs new file mode 100644 index 0000000000..39d599f41d --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Payments/CapturePaymentReason.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Core.Domain.Payments +{ + /// + /// The reason for automatic capturing of the payment amount. + /// + public enum CapturePaymentReason + { + /// + /// Capture payment because the order has been marked as shipped. + /// + OrderShipped = 0, + + /// + /// Capture payment because the order has been marked as delivered. + /// + OrderDelivered + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs index 3303f6ff04..7aad6a5667 100644 --- a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs @@ -31,5 +31,15 @@ public PaymentSettings() /// Gets or sets a value indicating whether we should bypass the payment method info page /// public bool BypassPaymentMethodInfo { get; set; } - } + + /// + /// Gets or sets the reason for automatic payment capturing + /// + public CapturePaymentReason? CapturePaymentReason { get; set; } + + /// + /// Gets or sets the identifier of the currency in which the wallet is kept. + /// + public int WalletCurrencyId { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs b/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs index eb08877672..71999bd5ba 100644 --- a/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Security/SecuritySettings.cs @@ -12,11 +12,6 @@ public SecuritySettings() AdminAreaAllowedIpAddresses = new List(); } - /// - /// Gets or sets a value indicating whether all pages will be forced to use SSL (no matter of a specified [RequireHttpsByConfigAttribute] attribute) - /// - public bool ForceSslForAllPages { get; set; } - /// /// When true, bypasses any SSL redirection on localhost /// @@ -36,5 +31,10 @@ public SecuritySettings() /// Gets or sets a vaule indicating whether to hide admin menu items based on ACL /// public bool HideAdminMenuItemsBasedOnPermissions { get; set; } + + /// + /// Gets or sets a vaule indicating whether "Honeypot" is enabled to prevent bots from posting forms. + /// + public bool EnableHoneypotProtection { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs index a597a45c72..7160bcf30c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs @@ -95,6 +95,8 @@ public SeoSettings() /// public bool LoadAllUrlAliasesOnStartup { get; set; } + public bool RedirectLegacyTopicUrls { get; set; } + #region XML Sitemap public bool XmlSitemapEnabled { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs index da3f0315cd..0fb734efbd 100644 --- a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs +++ b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs @@ -33,6 +33,12 @@ public partial class Store : BaseEntity [DataMember] public string SecureUrl { get; set; } + /// + /// Gets or sets a value indicating whether all pages will be forced to use SSL (no matter of a specified [RequireHttpsByConfigAttribute] attribute) + /// + [DataMember] + public bool ForceSslForAllPages { get; set; } + /// /// Gets or sets the comma separated list of possible HTTP_HOST values /// diff --git a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs index ad6dc64ea8..c6d22dd2e0 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs @@ -1,6 +1,5 @@ - -using System; -using System.ComponentModel.DataAnnotations; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics; using SmartStore.Core.Data.Hooks; @@ -11,6 +10,8 @@ namespace SmartStore.Core.Domain.Tasks [Hookable(false)] public class ScheduleTask : BaseEntity, ICloneable { + private ICollection _scheduleTaskHistory; + /// /// Gets or sets the name /// @@ -46,59 +47,46 @@ public class ScheduleTask : BaseEntity, ICloneable [Index("IX_NextRun_Enabled", 0)] public DateTime? NextRunUtc { get; set; } - [Index("IX_LastStart_LastEnd", 0)] - public DateTime? LastStartUtc { get; set; } - - [Index("IX_LastStart_LastEnd", 1)] - public DateTime? LastEndUtc { get; set; } - - public DateTime? LastSuccessUtc { get; set; } - - public string LastError { get; set; } - + /// + /// Indicates whether the task is hidden. + /// public bool IsHidden { get; set; } - /// - /// Gets or sets a value indicating the current percentual progress for a running task - /// - public int? ProgressPercent { get; set; } - - /// - /// Gets or sets the current progress message for a running task - /// - public string ProgressMessage { get; set; } - - /// - /// Concurrency Token - /// - [Timestamp] - public byte[] RowVersion { get; set; } - - /// - /// Gets a value indicating whether a task is running - /// - public bool IsRunning - { - get - { - var result = LastStartUtc.HasValue && LastStartUtc.Value > LastEndUtc.GetValueOrDefault(); - return result; - } - } + /// + /// Indicates whether the task is executed decidedly on each machine of a web farm. + /// + public bool RunPerMachine { get; set; } /// - /// Gets a value indicating whether a task is scheduled for execution (Enabled = true and NextRunUtc <= UtcNow ) + /// Gets a value indicating whether a task is scheduled for execution (Enabled = true and NextRunUtc <= UtcNow and is not running). /// public bool IsPending { get { - var result = Enabled && NextRunUtc.HasValue && NextRunUtc <= DateTime.UtcNow; + var result = Enabled && NextRunUtc.HasValue && NextRunUtc <= DateTime.UtcNow && (LastHistoryEntry == null || !LastHistoryEntry.IsRunning); return result; } } - public ScheduleTask Clone() + public ScheduleTaskHistory LastHistoryEntry { get; set; } + + /// + /// Gets or sets the schedule task history. + /// + public virtual ICollection ScheduleTaskHistory + { + get + { + return _scheduleTaskHistory ?? (_scheduleTaskHistory = new HashSet()); + } + protected set + { + _scheduleTaskHistory = value; + } + } + + public ScheduleTask Clone() { return (ScheduleTask)this.MemberwiseClone(); } diff --git a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTaskHistory.cs b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTaskHistory.cs new file mode 100644 index 0000000000..dbf2cbfefb --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTaskHistory.cs @@ -0,0 +1,76 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; +using SmartStore.Core.Data.Hooks; + +namespace SmartStore.Core.Domain.Tasks +{ + [Hookable(false)] + public class ScheduleTaskHistory : BaseEntity, ICloneable + { + /// + /// Gets or sets the schedule task identifier. + /// + public int ScheduleTaskId { get; set; } + + /// + /// Gets or sets whether the task is running. + /// + [Index("IX_MachineName_IsRunning", 1)] + public bool IsRunning { get; set; } + + /// + /// Gets or sets the server machine name. + /// + [Index("IX_MachineName_IsRunning", 0)] + public string MachineName { get; set; } + + /// + /// Gets or sets the date when the task was started. It is also the date when this entry was created. + /// + [Index("IX_Started_Finished", 0)] + public DateTime StartedOnUtc { get; set; } + + /// + /// Gets or sets the date when the task has been finished. + /// + [Index("IX_Started_Finished", 1)] + public DateTime? FinishedOnUtc { get; set; } + + /// + /// Gets or sets the date when the task succeeded. + /// + public DateTime? SucceededOnUtc { get; set; } + + /// + /// Gets or sets the last error message. + /// + public string Error { get; set; } + + /// + /// Gets or sets a value indicating the current percentual progress for a running task. + /// + public int? ProgressPercent { get; set; } + + /// + /// Gets or sets the current progress message for a running task. + /// + public string ProgressMessage { get; set; } + + /// + /// Gets or sets the schedule task. + /// + public virtual ScheduleTask ScheduleTask { get; set; } + + public ScheduleTaskHistory Clone() + { + var clone = (ScheduleTaskHistory)this.MemberwiseClone(); + clone.ScheduleTask = this.ScheduleTask.Clone(); + return clone; + } + + object ICloneable.Clone() + { + return Clone(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs index fa5ef2b93a..36a6d95ac3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs @@ -1,111 +1,162 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Topics { - /// - /// Represents a topic - /// - public partial class Topic : BaseEntity, ILocalizedEntity, IStoreMappingSupported + /// + /// Represents a topic + /// + [DataContract] + public partial class Topic : BaseEntity, ILocalizedEntity, ISlugSupported, IStoreMappingSupported, IAclSupported { - /// - /// Gets or sets the name - /// - public string SystemName { get; set; } - - /// - /// Gets or sets the value indicating whether this topic is deleteable by a user - /// - public bool IsSystemTopic { get; set; } - - /// - /// Gets or sets the value indicating whether this topic should be included in sitemap - /// - public bool IncludeInSitemap { get; set; } - - /// - /// Gets or sets the value indicating whether this topic is password protected - /// - public bool IsPasswordProtected { get; set; } - - /// - /// Gets or sets the password - /// - public string Password { get; set; } - - /// - /// Gets or sets the title - /// - public string Title { get; set; } - - /// - /// Gets or sets the body - /// - public string Body { get; set; } - - /// - /// Gets or sets the meta keywords - /// - public string MetaKeywords { get; set; } - - /// - /// Gets or sets the meta description - /// - public string MetaDescription { get; set; } - - /// - /// Gets or sets the meta title - /// - public string MetaTitle { get; set; } + public Topic() + { + IsPublished = true; + } + + /// + /// Gets or sets the name + /// + [DataMember] + public string SystemName { get; set; } + + /// + /// Gets or sets the value indicating whether this topic is deleteable by a user + /// + [DataMember] + public bool IsSystemTopic { get; set; } + + /// + /// Gets or sets the value indicating whether this topic should be included in sitemap + /// + [DataMember] + public bool IncludeInSitemap { get; set; } + + /// + /// Gets or sets the value indicating whether this topic is password protected + /// + [DataMember] + public bool IsPasswordProtected { get; set; } + + /// + /// Gets or sets the password + /// + [DataMember] + public string Password { get; set; } + + /// + /// Gets or sets the title + /// + [DataMember] + public string Title { get; set; } + + /// + /// Gets or sets the short title (for links) + /// + [DataMember] + public string ShortTitle { get; set; } + + /// + /// Gets or sets the intro + /// + [DataMember] + public string Intro { get; set; } + + /// + /// Gets or sets the body + /// + [DataMember] + public string Body { get; set; } + + /// + /// Gets or sets the meta keywords + /// + [DataMember] + public string MetaKeywords { get; set; } + + /// + /// Gets or sets the meta description + /// + [DataMember] + public string MetaDescription { get; set; } + + /// + /// Gets or sets the meta title + /// + [DataMember] + public string MetaTitle { get; set; } /// /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores /// + [DataMember] public bool LimitedToStores { get; set; } - /// - /// Gets or sets a value indicating whether the topic should also be rendered as a generic html widget - /// - public bool RenderAsWidget { get; set; } + /// + /// Gets or sets a value indicating whether the topic should also be rendered as a generic html widget + /// + [DataMember] + public bool RenderAsWidget { get; set; } - /// - /// Gets or sets the widget zone name - /// - public string WidgetZone { get; set; } + /// + /// Gets or sets the widget zone name + /// + [DataMember] + public string WidgetZone { get; set; } /// /// Gets or sets a value indicating whether the content should be surrounded by a topic block wrapper /// + [DataMember] public bool? WidgetWrapContent { get; set; } - /// - /// Gets or sets a value indicating whether the title should be displayed in the widget block - /// - public bool WidgetShowTitle { get; set; } - - /// - /// Gets or sets a value indicating whether the widget block should have borders - /// - public bool WidgetBordered { get; set; } - - /// - /// Gets or sets the sort order (relevant for widgets) - /// - public int Priority { get; set; } - - /// - /// Gets or sets the title tag - /// - public string TitleTag { get; set; } - - /// - /// Helper function which gets the comma-separated WidgetZone property as list of strings - /// - /// - public IEnumerable GetWidgetZones() + /// + /// Gets or sets a value indicating whether the title should be displayed in the widget block + /// + [DataMember] + public bool WidgetShowTitle { get; set; } + + /// + /// Gets or sets a value indicating whether the widget block should have borders + /// + [DataMember] + public bool WidgetBordered { get; set; } + + /// + /// Gets or sets the sort order (relevant for widgets) + /// + [DataMember] + public int Priority { get; set; } + + /// + /// Gets or sets the title tag + /// + [DataMember] + public string TitleTag { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is subject to ACL + /// + [DataMember] + public bool SubjectToAcl { get; set; } + + /// + /// Gets or sets a value indicating whether the topic page is published + /// + [DataMember] + public bool IsPublished { get; set; } + + /// + /// Helper function which gets the comma-separated WidgetZone property as list of strings + /// + /// + public IEnumerable GetWidgetZones() { if (this.WidgetZone.IsEmpty()) { diff --git a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs index 3070172cdc..746f45a85d 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs @@ -2,6 +2,7 @@ using System.Xml; using System.Globalization; using TimeZone = System.TimeZoneInfo; +using System.Text.RegularExpressions; namespace SmartStore { @@ -12,21 +13,21 @@ public static class DateTimeExtensions /// /// Converts a nullable date/time value to UTC. /// - /// The nullable date/time + /// The nullable date/time /// The nullable date/time in UTC - public static DateTime? ToUniversalTime(this DateTime? dateTime) + public static DateTime? ToUniversalTime(this DateTime? value) { - return dateTime.HasValue ? dateTime.Value.ToUniversalTime() : (DateTime?)null; + return value.HasValue ? value.Value.ToUniversalTime() : (DateTime?)null; } /// /// Converts a nullable UTC date/time value to local time. /// - /// The nullable UTC date/time + /// The nullable UTC date/time /// The nullable UTC date/time as local time - public static DateTime? ToLocalTime(this DateTime? dateTime) + public static DateTime? ToLocalTime(this DateTime? value) { - return dateTime.HasValue ? dateTime.Value.ToLocalTime() : (DateTime?)null; + return value.HasValue ? value.Value.ToLocalTime() : (DateTime?)null; } @@ -39,16 +40,16 @@ public static class DateTimeExtensions /// date's 'day' will be promoted, and the time will be set to 00:00:00. ///

/// - /// the Date to round, if the current time will + /// the Date to round, if the current time will /// be used /// the new rounded date - public static DateTime GetEvenHourDate(this DateTime? dateTime) + public static DateTime GetEvenHourDate(this DateTime? value) { - if (!dateTime.HasValue) + if (!value.HasValue) { - dateTime = DateTime.UtcNow; + value = DateTime.UtcNow; } - DateTime d = dateTime.Value.AddHours(1); + DateTime d = value.Value.AddHours(1); return new DateTime(d.Year, d.Month, d.Day, d.Hour, 0, 0); } @@ -61,16 +62,16 @@ public static DateTime GetEvenHourDate(this DateTime? dateTime) /// then the hour (and possibly the day) will be promoted. ///

/// - /// The Date to round, if the current time will be used + /// The Date to round, if the current time will be used /// The new rounded date - public static DateTime GetEvenMinuteDate(this DateTime? dateTime) + public static DateTime GetEvenMinuteDate(this DateTime? value) { - if (!dateTime.HasValue) + if (!value.HasValue) { - dateTime = DateTime.UtcNow; + value = DateTime.UtcNow; } - DateTime d = dateTime.Value; + DateTime d = value.Value; d = d.AddMinutes(1); return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0); } @@ -83,23 +84,23 @@ public static DateTime GetEvenMinuteDate(this DateTime? dateTime) /// with the time of 08:13:00. ///

/// - /// the Date to round, if the current time will + /// the Date to round, if the current time will /// be used /// the new rounded date - public static DateTime GetEvenMinuteDateBefore(this DateTime? dateTime) + public static DateTime GetEvenMinuteDateBefore(this DateTime? value) { - if (!dateTime.HasValue) + if (!value.HasValue) { - dateTime = DateTime.UtcNow; + value = DateTime.UtcNow; } - DateTime d = dateTime.Value; + DateTime d = value.Value; return new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0); } - public static long ToJavaScriptTicks(this DateTime dateTime) + public static long ToJavaScriptTicks(this DateTime value) { - DateTimeOffset utcDateTime = dateTime.ToUniversalTime(); + DateTimeOffset utcDateTime = value.ToUniversalTime(); long javaScriptTicks = (utcDateTime.Ticks - BeginOfEpoch.Ticks) / (long)10000; return javaScriptTicks; } @@ -108,11 +109,11 @@ public static long ToJavaScriptTicks(this DateTime dateTime) /// Get the first day of the month for /// any full date submitted /// - /// + /// /// - public static DateTime GetFirstDayOfMonth(this DateTime date) + public static DateTime GetFirstDayOfMonth(this DateTime value) { - DateTime dtFrom = date; + DateTime dtFrom = value; dtFrom = dtFrom.AddDays(-(dtFrom.Day - 1)); return dtFrom; } @@ -121,39 +122,76 @@ public static DateTime GetFirstDayOfMonth(this DateTime date) /// Get the last day of the month for any /// full date /// - /// + /// /// - public static DateTime GetLastDayOfMonth(this DateTime date) + public static DateTime GetLastDayOfMonth(this DateTime value) { - DateTime dtTo = date; + DateTime dtTo = value; dtTo = dtTo.AddMonths(1); dtTo = dtTo.AddDays(-(dtTo.Day)); return dtTo; } - public static DateTime ToEndOfTheDay(this DateTime dt) + public static DateTime ToEndOfTheDay(this DateTime value) { - if (dt != null) - return new DateTime(dt.Year, dt.Month, dt.Day, 23, 59, 59); - return dt; + if (value != null) + return new DateTime(value.Year, value.Month, value.Day, 23, 59, 59); + return value; } - public static DateTime? ToEndOfTheDay(this DateTime? dt) + public static DateTime? ToEndOfTheDay(this DateTime? value) { - return (dt.HasValue ? dt.Value.ToEndOfTheDay() : dt); + return (value.HasValue ? value.Value.ToEndOfTheDay() : value); } - /// Epoch time. Number of seconds since midnight (UTC) on 1st January 1970. - public static long ToUnixTime(this DateTime date) + /// + /// Epoch time. Number of seconds since midnight (UTC) on 1st January 1970. + /// + public static long ToUnixTime(this DateTime value) { - return Convert.ToInt64((date.ToUniversalTime() - BeginOfEpoch).TotalSeconds); + return Convert.ToInt64((value.ToUniversalTime() - BeginOfEpoch).TotalSeconds); } - /// UTC date based on number of seconds since midnight (UTC) on 1st January 1970. + /// + /// UTC date based on number of seconds since midnight (UTC) on 1st January 1970. + /// public static DateTime FromUnixTime(this long unixTime) { return BeginOfEpoch.AddSeconds(unixTime); } + + /// + /// Converts a DateTime to a string with native digits + /// + public static string ToNativeString(this DateTime value) + { + return value.ToNativeString(null, null); + } + + /// + /// Converts a DateTime to a string with native digits + /// + public static string ToNativeString(this DateTime value, IFormatProvider provider) + { + return value.ToNativeString(null, provider); + } + + /// + /// Converts a DateTime to a string with native digits + /// + public static string ToNativeString(this DateTime value, string format) + { + return value.ToNativeString(format, null); + } + + /// + /// Converts a DateTime to a string with native digits + /// + public static string ToNativeString(this DateTime value, string format, IFormatProvider provider) + { + provider = provider ?? CultureInfo.CurrentCulture; + return value.ToString(format, provider).ReplaceNativeDigits(provider); + } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs deleted file mode 100644 index 83e67555a0..0000000000 --- a/src/Libraries/SmartStore.Core/Extensions/DecimalExtensions.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Globalization; -using SmartStore.Core.Domain.Directory; - -namespace SmartStore -{ - public static class DecimalExtensions - { - /// - /// Calculates the tax (percentage) from a gross and a net value. - /// - /// Gross value - /// Net value - /// Rounding decimal number - /// Tax percentage - public static decimal ToTaxPercentage(this decimal inclTax, decimal exclTax, int? decimals = null) - { - if (exclTax == decimal.Zero) - { - return decimal.Zero; - } - - var result = ((inclTax / exclTax) - 1.0M) * 100.0M; - - return (decimals.HasValue ? Math.Round(result, decimals.Value) : result); - } - - /// - /// Converts to smallest currency uint, e.g. cents - /// - /// Handling of the midway between two numbers. "ToEven" round down, "AwayFromZero" round up. - /// Smallest currency unit - public static int ToSmallestCurrencyUnit(this decimal value, MidpointRounding midpoint = MidpointRounding.AwayFromZero) - { - var result = Math.Round(value * 100, 0, midpoint); - return Convert.ToInt32(result); - } - - /// - /// Round decimal to the nearest multiple of denomination - /// - /// Value to round - /// Denomination - /// Handling of the midway between two numbers. "ToEven" round down, "AwayFromZero" round up. - /// Rounded value - public static decimal RoundToNearest(this decimal value, decimal denomination, MidpointRounding midpoint = MidpointRounding.AwayFromZero) - { - if (denomination == decimal.Zero) - { - return value; - } - - return Math.Round(value / denomination, midpoint) * denomination; - } - - /// - /// Round decimal up or down to the nearest multiple of denomination - /// - /// Value to round - /// Denomination - /// true round to, false round down - /// Rounded value - public static decimal RoundToNearest(this decimal value, decimal denomination, bool roundUp) - { - if (denomination == decimal.Zero) - { - return value; - } - - var roundedValueBase = roundUp - ? Math.Ceiling(value / denomination) - : Math.Floor(value / denomination); - - return Math.Round(roundedValueBase) * denomination; - } - - /// - /// Round decimal up or down to the nearest multiple of denomination if activated for currency - /// - /// Value to round - /// Currency. Rounding must be activated for this currency. - /// The rounding amount - /// Rounded value - public static decimal RoundToNearest(this decimal value, Currency currency, out decimal roundingAmount) - { - var oldValue = value; - - switch (currency.RoundOrderTotalRule) - { - case CurrencyRoundingRule.RoundMidpointUp: - value = value.RoundToNearest(currency.RoundOrderTotalDenominator, MidpointRounding.AwayFromZero); - break; - case CurrencyRoundingRule.AlwaysRoundDown: - value = value.RoundToNearest(currency.RoundOrderTotalDenominator, false); - break; - case CurrencyRoundingRule.AlwaysRoundUp: - value = value.RoundToNearest(currency.RoundOrderTotalDenominator, true); - break; - case CurrencyRoundingRule.RoundMidpointDown: - default: - value = value.RoundToNearest(currency.RoundOrderTotalDenominator, MidpointRounding.ToEven); - break; - } - - roundingAmount = value - Math.Round(oldValue, 2); - - return value; - } - - /// - /// Rounds a value if rounding is enabled for the currency - /// - /// Value to round - /// Currency - /// Rounded value - public static decimal RoundIfEnabledFor(this decimal value, Currency currency) - { - Guard.NotNull(currency, nameof(currency)); - - if (currency.RoundOrderItemsEnabled) - { - return Math.Round(value, currency.RoundNumDecimals); - } - - return value; - } - - /// - /// Rounds and formats a decimal culture invariant - /// - /// Value to round - /// Rounding decimal number - /// Rounded and formated value - public static string FormatInvariant(this decimal value, int decimals = 2) - { - return Math.Round(value, decimals).ToString("0.00", CultureInfo.InvariantCulture); - } - } -} diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index 87abdd2e99..b3b7be8905 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -286,6 +286,11 @@ join entity in source on id equals entity.Id return sorted; } + public static string StrJoin(this IEnumerable source, string separator) + { + return string.Join(separator, source); + } + #endregion #region Multimap @@ -295,25 +300,34 @@ public static Multimap ToMultimap( Func keySelector, Func valueSelector) { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(keySelector, nameof(keySelector)); - Guard.NotNull(valueSelector, nameof(valueSelector)); + return source.ToMultimap(keySelector, valueSelector, null); + } - var map = new Multimap(); + public static Multimap ToMultimap( + this IEnumerable source, + Func keySelector, + Func valueSelector, + IEqualityComparer comparer) + { + Guard.NotNull(source, nameof(source)); + Guard.NotNull(keySelector, nameof(keySelector)); + Guard.NotNull(valueSelector, nameof(valueSelector)); - foreach (var item in source) - { - map.Add(keySelector(item), valueSelector(item)); - } + var map = new Multimap(comparer); - return map; - } + foreach (var item in source) + { + map.Add(keySelector(item), valueSelector(item)); + } - #endregion + return map; + } + + #endregion - #region NameValueCollection + #region NameValueCollection - public static void AddRange(this NameValueCollection initial, NameValueCollection other) + public static void AddRange(this NameValueCollection initial, NameValueCollection other) { Guard.NotNull(initial, "initial"); diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 7810a19b12..8dfa07887e 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -18,6 +18,7 @@ public static class HttpExtensions { private const string HTTP_CLUSTER_VAR = "HTTP_CLUSTER_HTTPS"; private const string HTTP_XFWDPROTO_VAR = "HTTP_X_FORWARDED_PROTO"; + private const string CACHE_REGION_NAME = "SmartStoreNET:"; /// /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). @@ -140,7 +141,7 @@ private static void CopyCookie(HttpWebRequest webRequest, HttpRequestBase source public static string BuildScopedKey(this Cache cache, string key) { - return key.HasValue() ? "SmartStoreNET:" + key : null; + return key.HasValue() ? CACHE_REGION_NAME + key : null; } public static T GetOrAdd(this Cache cache, string key, Func acquirer, TimeSpan? duration = null) @@ -170,11 +171,6 @@ public static T GetOrAdd(this Cache cache, string key, Func acquirer, Time public static T GetItem(this HttpContext httpContext, string key, Func factory = null, bool forceCreation = true) { - if (httpContext?.Items == null) - { - return default(T); - } - return GetItem(new HttpContextWrapper(httpContext), key, factory, forceCreation); } @@ -208,22 +204,27 @@ public static T GetItem(this HttpContextBase httpContext, string key, Func public static void RemoveByPattern(this Cache cache, string pattern) { - var regionName = "SmartStoreNET:"; + var keys = cache.AllKeys(pattern); - pattern = pattern == "*" ? regionName : pattern; + foreach (var key in keys.ToArray()) + { + cache.Remove(key); + } + } + + public static string[] AllKeys(this Cache cache, string pattern) + { + pattern = pattern == "*" ? CACHE_REGION_NAME : pattern; var keys = from entry in HttpRuntime.Cache.AsParallel().Cast() let key = entry.Key.ToString() where key.StartsWith(pattern, StringComparison.OrdinalIgnoreCase) select key; - foreach (var key in keys.ToArray()) - { - cache.Remove(key); - } + return keys.ToArray(); } - public static ControllerContext GetMasterControllerContext(this ControllerContext controllerContext) + public static ControllerContext GetMasterControllerContext(this ControllerContext controllerContext) { Guard.NotNull(controllerContext, nameof(controllerContext)); diff --git a/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs index d176d04adb..5b6c628c10 100644 --- a/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs @@ -8,8 +8,7 @@ namespace SmartStore { public static class IOExtensions - { - + { public static bool IsFileLocked(this FileInfo file) { if (file == null) @@ -23,10 +22,10 @@ public static bool IsFileLocked(this FileInfo file) } catch (IOException) { - //the file is unavailable because it is: - //still being written to - //or being processed by another thread - //or does not exist (has already been processed) + // the file is unavailable because it is: + // still being written to + // or being processed by another thread + // or does not exist (has already been processed) return true; } finally @@ -39,5 +38,4 @@ public static bool IsFileLocked(this FileInfo file) return false; } } - } diff --git a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs index f4452fb5ed..d687cd92b1 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -1,11 +1,6 @@ using System; -using System.ComponentModel; -using System.Data; -using System.Data.OleDb; using System.Diagnostics; using System.Text; -using System.Web.Routing; -using SmartStore.Core; namespace SmartStore { @@ -51,7 +46,9 @@ public static bool IsNullOrDefault(this T? value) where T : struct return default(T).Equals(value.GetValueOrDefault()); } - /// Converts bytes into a hex string. + /// + /// Converts bytes into a hex string. + /// public static string ToHexString(this byte[] bytes, int length = 0) { if (bytes == null || bytes.Length <= 0) diff --git a/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs new file mode 100644 index 0000000000..ebaccbd53b --- /dev/null +++ b/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs @@ -0,0 +1,159 @@ +using System; +using System.Globalization; +using SmartStore.Core.Domain.Directory; + +namespace SmartStore +{ + public static class NumericExtensions + { + #region int + + public static int GetRange(this int id, int size, out int lower) + { + lower = 0; + + // max 1000 values per cache item + var range = (int)Math.Ceiling((decimal)id / size) * size; + + lower = range - (size - 1); + + return range; + } + + #endregion + + #region decimal + + /// + /// Calculates the tax (percentage) from a gross and a net value. + /// + /// Gross value + /// Net value + /// Rounding decimal number + /// Tax percentage + public static decimal ToTaxPercentage(this decimal inclTax, decimal exclTax, int? decimals = null) + { + if (exclTax == decimal.Zero) + { + return decimal.Zero; + } + + var result = ((inclTax / exclTax) - 1.0M) * 100.0M; + + return (decimals.HasValue ? Math.Round(result, decimals.Value) : result); + } + + /// + /// Converts to smallest currency uint, e.g. cents + /// + /// Handling of the midway between two numbers. "ToEven" round down, "AwayFromZero" round up. + /// Smallest currency unit + public static int ToSmallestCurrencyUnit(this decimal value, MidpointRounding midpoint = MidpointRounding.AwayFromZero) + { + var result = Math.Round(value * 100, 0, midpoint); + return Convert.ToInt32(result); + } + + /// + /// Round decimal to the nearest multiple of denomination + /// + /// Value to round + /// Denomination + /// Handling of the midway between two numbers. "ToEven" round down, "AwayFromZero" round up. + /// Rounded value + public static decimal RoundToNearest(this decimal value, decimal denomination, MidpointRounding midpoint = MidpointRounding.AwayFromZero) + { + if (denomination == decimal.Zero) + { + return value; + } + + return Math.Round(value / denomination, midpoint) * denomination; + } + + /// + /// Round decimal up or down to the nearest multiple of denomination + /// + /// Value to round + /// Denomination + /// true round to, false round down + /// Rounded value + public static decimal RoundToNearest(this decimal value, decimal denomination, bool roundUp) + { + if (denomination == decimal.Zero) + { + return value; + } + + var roundedValueBase = roundUp + ? Math.Ceiling(value / denomination) + : Math.Floor(value / denomination); + + return Math.Round(roundedValueBase) * denomination; + } + + /// + /// Round decimal up or down to the nearest multiple of denomination if activated for currency + /// + /// Value to round + /// Currency. Rounding must be activated for this currency. + /// The rounding amount + /// Rounded value + public static decimal RoundToNearest(this decimal value, Currency currency, out decimal roundingAmount) + { + var oldValue = value; + + switch (currency.RoundOrderTotalRule) + { + case CurrencyRoundingRule.RoundMidpointUp: + value = value.RoundToNearest(currency.RoundOrderTotalDenominator, MidpointRounding.AwayFromZero); + break; + case CurrencyRoundingRule.AlwaysRoundDown: + value = value.RoundToNearest(currency.RoundOrderTotalDenominator, false); + break; + case CurrencyRoundingRule.AlwaysRoundUp: + value = value.RoundToNearest(currency.RoundOrderTotalDenominator, true); + break; + case CurrencyRoundingRule.RoundMidpointDown: + default: + value = value.RoundToNearest(currency.RoundOrderTotalDenominator, MidpointRounding.ToEven); + break; + } + + roundingAmount = value - Math.Round(oldValue, 2); + + return value; + } + + /// + /// Rounds a value if rounding is enabled for the currency + /// + /// Value to round + /// Currency + /// Rounded value + public static decimal RoundIfEnabledFor(this decimal value, Currency currency) + { + Guard.NotNull(currency, nameof(currency)); + + if (currency.RoundOrderItemsEnabled) + { + return Math.Round(value, currency.RoundNumDecimals); + } + + return value; + } + + /// + /// Rounds and formats a decimal culture invariant + /// + /// Value to round + /// Rounding decimal number + /// Rounded and formated value + public static string FormatInvariant(this decimal value, int decimals = 2) + { + return Math.Round(value, decimals).ToString("0.00", CultureInfo.InvariantCulture); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs index 4301d61e64..70bbe2eb28 100644 --- a/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs @@ -14,6 +14,7 @@ public static string GetAreaName(this RouteData routeData) { return (area as string); } + return routeData.Route.GetAreaName(); } @@ -24,11 +25,13 @@ public static string GetAreaName(this RouteBase route) { return area.Area; } + var route2 = route as Route; if ((route2 != null) && (route2.DataTokens != null)) { return (route2.DataTokens["area"] as string); } + return null; } diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index f435df42ac..8d004331ed 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -740,18 +740,27 @@ public static string[] SplitSafe(this string value, string separator) /// true: success, false: failure [DebuggerStepThrough] [SuppressMessage("ReSharper", "StringIndexOfIsCultureSpecific.1")] - public static bool SplitToPair(this string value, out string strLeft, out string strRight, string delimiter) + public static bool SplitToPair(this string value, out string leftPart, out string rightPart, string delimiter, bool splitAfterLast = false) { - int idx = -1; - if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(delimiter) || (idx = value.IndexOf(delimiter)) == -1) + leftPart = value; + rightPart = ""; + + if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(delimiter)) + { + return false; + } + + var idx = splitAfterLast + ? value.LastIndexOf(delimiter) + : value.IndexOf(delimiter); + + if (idx == -1) { - strLeft = value; - strRight = ""; return false; } - strLeft = value.Substring(0, idx); - strRight = value.Substring(idx + delimiter.Length); + leftPart = value.Substring(0, idx); + rightPart = value.Substring(idx + delimiter.Length); return true; } @@ -883,7 +892,9 @@ public static string ToAttribute(this string value, string name, bool htmlEncode return string.Format(" {0}=\"{1}\"", name, htmlEncode ? HttpUtility.HtmlEncode(value) : value); } - /// Appends grow and uses delimiter if the string is not empty. + /// + /// Appends grow and uses delimiter if the string is not empty. + /// [DebuggerStepThrough] public static string Grow(this string value, string grow, string delimiter) { @@ -893,7 +904,7 @@ public static string Grow(this string value, string grow, string delimiter) if (string.IsNullOrEmpty(grow)) return (string.IsNullOrEmpty(value) ? "" : value); - return string.Format("{0}{1}{2}", value, delimiter, grow); + return string.Concat(value, delimiter, grow); } /// Returns n/a if string is empty else self. @@ -939,6 +950,29 @@ public static string Replace(this string value, string oldValue, string newValue return value; } + /// + /// Replaces digits in a string with culture native digits (if digit substitution for culture is required) + /// + [DebuggerStepThrough] + public static string ReplaceNativeDigits(this string value, IFormatProvider provider = null) + { + Guard.NotNull(value, nameof(value)); + + provider = provider ?? NumberFormatInfo.CurrentInfo; + var nfi = NumberFormatInfo.GetInstance(provider); + + if (nfi.DigitSubstitution == DigitShapes.None) + { + return value; + } + + var nativeDigits = nfi.NativeDigits; + var rg = new Regex(@"\d"); + + var result = rg.Replace(value, m => nativeDigits[m.Value.ToInt()]); + return result; + } + [DebuggerStepThrough] public static string TrimSafe(this string value) { diff --git a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs index 98e482d393..3e645dcc1f 100644 --- a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs +++ b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs @@ -294,7 +294,7 @@ public static string RelativizeFontSizes(string html, int baseFontSizePx = 16) { if (node.Style.FontSize is string s && s.EndsWith("px")) { - var size = s.Substring(0, s.Length - 2).Convert(); + var size = s.Substring(0, s.Length - 2).Convert(); if (size > 0) { //node.Style.FontSize = Math.Round(((double)size / (double)baseFontSizePx), 4) + "em"; diff --git a/src/Libraries/SmartStore.Core/IO/IFile.cs b/src/Libraries/SmartStore.Core/IO/IFile.cs index 0620feadd8..6d86299fc8 100644 --- a/src/Libraries/SmartStore.Core/IO/IFile.cs +++ b/src/Libraries/SmartStore.Core/IO/IFile.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; using System.IO; using System.Threading.Tasks; @@ -6,14 +7,46 @@ namespace SmartStore.Core.IO { public interface IFile { - string Path { get; } - string Name { get; } + /// + /// The path relative to the storage root + /// + string Path { get; } + + /// + /// The path without the file part, but with trailing slash + /// + string Directory { get; } + + /// + /// File name including extension + /// + string Name { get; } + + /// + /// File name excluding extension + /// + string Title { get; } + + /// + /// Size in bytes + /// long Size { get; } + /// /// Expressed as UTC time /// DateTime LastUpdated { get; } - string FileType { get; } + + /// + /// File extension including dot + /// + string Extension { get; } + + /// + /// Dimensions, if the file is an image. + /// + Size Dimensions { get; } + bool Exists { get; } /// diff --git a/src/Libraries/SmartStore.Core/IO/IFileSystem.cs b/src/Libraries/SmartStore.Core/IO/IFileSystem.cs index 2511c117c2..c90b886ffb 100644 --- a/src/Libraries/SmartStore.Core/IO/IFileSystem.cs +++ b/src/Libraries/SmartStore.Core/IO/IFileSystem.cs @@ -21,8 +21,12 @@ public interface IFileSystem /// Retrieves the public URL for a given file within the storage provider. /// /// The relative path within the storage provider. + /// + /// If true and the storage is in the cloud, returns the actual remote cloud URL to the resource. + /// If false, retrieves an app relative URL to delegate further processing to the media middleware (which can handle remote files) + /// /// The public URL. - string GetPublicUrl(string path); + string GetPublicUrl(string path, bool forCloud = false); /// /// Retrieves the path within the storage provider for a given public url. @@ -69,12 +73,24 @@ public interface IFileSystem /// If the file or the folder is not found. IFolder GetFolderForFile(string path); + /// + /// Retrieves the count of files within a path. + /// + /// The relative path to the folder in which to retrieve file count. + /// The file pattern to match + /// Optional. Files matching the predicate are excluded. + /// Whether to count files in all subfolders also + /// Total count of files. + long CountFiles(string path, string pattern, Func predicate, bool deep = true); + /// /// Performs a deep search for files within a path. /// /// The relative path to the folder in which to process file search. + /// The file pattern to match + /// Whether to search in all subfolders also /// Matching file names - IEnumerable SearchFiles(string path, string pattern); + IEnumerable SearchFiles(string path, string pattern, bool deep = true); /// /// Lists the files within a storage provider's path. diff --git a/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs b/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs index 673dab05dd..755aee74e1 100644 --- a/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs +++ b/src/Libraries/SmartStore.Core/IO/IFileSystemExtensions.cs @@ -222,5 +222,68 @@ public static bool TryCreateFolder(this IFileSystem fileSystem, string path) return true; } + /// + /// Checks whether the name of the file is unique within its directory. + /// When given file exists, this method appends [1...n] to the file title until + /// the check returns false. + /// + /// The path of file to check + /// An object containing the unique file's info, or null if method returns false + /// + /// false when does not exist yet. true otherwise. + /// + public static bool CheckFileUniqueness(this IFileSystem fileSystem, string path, out IFile uniqueFile) + { + Guard.NotEmpty(path, nameof(path)); + + uniqueFile = null; + + var file = fileSystem.GetFile(path); + if (!file.Exists) + { + return false; + } + + var pattern = string.Concat(file.Title, "-*", file.Extension); + var dir = file.Directory; + var files = new HashSet(fileSystem.SearchFiles(dir, pattern, false).Select(x => Path.GetFileName(x))); + + int i = 1; + while (true) + { + var newFileName = string.Concat(file.Title, "-", i, file.Extension); + if (!files.Contains(newFileName)) + { + // Found our gap + uniqueFile = fileSystem.GetFile(string.Concat(dir, newFileName)); + return true; + } + + i++; + } + } + + /// + /// Retrieves the count of files within a path. + /// + /// The relative path to the folder in which to retrieve file count. + /// The file pattern to match + /// Whether to count files in all subfolders also + /// Total count of files. + public static long CountFiles(this IFileSystem fileSystem, string path, string pattern, bool deep = true) + { + return fileSystem.CountFiles(path, pattern, null, deep); + } + + /// + /// Retrieves the count of files within a path. + /// + /// The relative path to the folder in which to retrieve file count. + /// Whether to count files in all subfolders also + /// Total count of files. + public static long CountFiles(this IFileSystem fileSystem, string path, bool deep = true) + { + return fileSystem.CountFiles(path, "*", null, deep); + } } } diff --git a/src/Libraries/SmartStore.Core/IO/ImageHeader.cs b/src/Libraries/SmartStore.Core/IO/ImageHeader.cs new file mode 100644 index 0000000000..c359438b7d --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/ImageHeader.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; + +namespace SmartStore.Core.IO +{ + /// + /// Taken from http://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/111349 + /// Minor improvements including supporting unsigned 16-bit integers when decoding Jfif and added logic + /// to load the image using new Bitmap if reading the headers fails + /// + public static class ImageHeader + { + internal class UnknownImageFormatException : ArgumentException + { + public UnknownImageFormatException(string paramName = "", Exception e = null) + : base("Could not recognise image format.", paramName, e) + { + } + } + + private static Dictionary> _imageFormatDecoders = new Dictionary>() + { + { new byte[] { 0x42, 0x4D }, DecodeBitmap }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, + { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, + // { new byte[] { 0xff, 0xd8 }, DecodeJfif }, + //{ new byte[] { 0xff, 0xd8, 0xff, 0xe0 }, DecodeJpeg }, + //{ new byte[] { 0xff }, DecodeJpeg2 }, + }; + + private static int _maxMagicBytesLength = 0; + + static ImageHeader() + { + _maxMagicBytesLength = _imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; + } + + /// + /// Gets the dimensions of an image. + /// + /// The path of the image to get the dimensions for. + /// The dimensions of the specified image. + public static Size GetDimensions(string path) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException("File '{0}' does not exist.".FormatInvariant(path)); + } + + var mime = MimeTypes.MapNameToMimeType(path); + return GetDimensions(File.OpenRead(path), mime, false); + } + + /// + /// Gets the dimensions of an image. + /// + /// The bytes of the image to get the dimensions for. + /// The MIME type of the image. Can be null. + /// The dimensions of the specified image. + public static Size GetDimensions(byte[] buffer, string mime = null) + { + if (buffer == null || buffer.Length == 0) + { + return Size.Empty; + } + + return GetDimensions(new MemoryStream(buffer), mime, false); + } + + /// + /// Gets the dimensions of an image. + /// + /// The stream of the image to get the dimensions for. + /// If false, the passed stream will get disposed + /// The dimensions of the specified image. + public static Size GetDimensions(Stream input, bool leaveOpen = true) + { + return GetDimensions(input, null, leaveOpen); + } + + /// + /// Gets the dimensions of an image. + /// + /// The stream of the image to get the dimensions for. + /// The MIME type of the image. Can be null. + /// If false, the passed stream will get disposed + /// The dimensions of the specified image. + public static Size GetDimensions(Stream input, string mime, bool leaveOpen = true) + { + Guard.NotNull(input, nameof(input)); + + var gdip = false; + + if (!input.CanSeek || input.Length == 0) + { + return Size.Empty; + } + + try + { + if (mime == "image/jpeg") + { + // Reading JPEG header does not work reliably + gdip = true; + return GetDimensionsByGdip(input); + } + + using (var reader = new BinaryReader(input, Encoding.Unicode, true)) + { + return GetDimensions(reader); + } + } + catch (Exception ex) + { + if (gdip) + { + throw ex; + } + + // something went wrong with fast image access, + // so get original size the classic way + try + { + input.Seek(0, SeekOrigin.Begin); + return GetDimensionsByGdip(input); + } + catch + { + throw ex; + } + } + finally + { + if (!leaveOpen) + { + input.Dispose(); + } + } + } + + /// + /// Gets the dimensions of an image. + /// + /// The path of the image to get the dimensions of. + /// The dimensions of the specified image. + /// The image was of an unrecognised format. + public static Size GetDimensions(BinaryReader binaryReader) + { + byte[] magicBytes = new byte[_maxMagicBytesLength]; + for (int i = 0; i < _maxMagicBytesLength; i += 1) + { + magicBytes[i] = binaryReader.ReadByte(); + foreach (var kvPair in _imageFormatDecoders) + { + if (StartsWith(magicBytes, kvPair.Key)) + { + return kvPair.Value(binaryReader); + } + } + } + + throw new UnknownImageFormatException("binaryReader"); + } + + private static Size GetDimensionsByGdip(Stream input) + { + using (var b = Image.FromStream(input, false, false)) + { + return new Size(b.Width, b.Height); + } + } + + private static bool StartsWith(byte[] thisBytes, byte[] thatBytes) + { + for (int i = 0; i < thatBytes.Length; i += 1) + { + if (thisBytes[i] != thatBytes[i]) + { + return false; + } + } + + return true; + } + + private static short ReadLittleEndianInt16(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(short)]; + + for (int i = 0; i < sizeof(short); i += 1) + { + bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); + } + return BitConverter.ToInt16(bytes, 0); + } + + private static ushort ReadLittleEndianUInt16(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(ushort)]; + + for (int i = 0; i < sizeof(ushort); i += 1) + { + bytes[sizeof(ushort) - 1 - i] = binaryReader.ReadByte(); + } + return BitConverter.ToUInt16(bytes, 0); + } + + private static int ReadLittleEndianInt32(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(int)]; + for (int i = 0; i < sizeof(int); i += 1) + { + bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); + } + return BitConverter.ToInt32(bytes, 0); + } + + private static Size DecodeBitmap(BinaryReader binaryReader) + { + binaryReader.ReadBytes(16); + int width = binaryReader.ReadInt32(); + int height = binaryReader.ReadInt32(); + return new Size(width, height); + } + + private static Size DecodeGif(BinaryReader binaryReader) + { + int width = binaryReader.ReadInt16(); + int height = binaryReader.ReadInt16(); + return new Size(width, height); + } + + private static Size DecodePng(BinaryReader binaryReader) + { + binaryReader.ReadBytes(8); + int width = ReadLittleEndianInt32(binaryReader); + int height = ReadLittleEndianInt32(binaryReader); + return new Size(width, height); + } + + #region Experiments + + private static Size DecodeJpeg(BinaryReader reader) + { + // For JPEGs, we need to read the first 12 bytes of each chunk. + // We'll read those 12 bytes at buf+2...buf+14, i.e. overwriting the existing buf. + + var buf = (new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }).Concat(reader.ReadBytes(20)).ToArray(); + + using (var f = new MemoryStream(buf)) + { + if (buf[6] == (byte)'J' && buf[7] == (byte)'F' && buf[8] == (byte)'I' && buf[9] == (byte)'F') + { + var len = buf.Length; + long pos = 2; + while (buf[2] == 0xFF) + { + if (buf[3] == 0xC0 || buf[3] == 0xC1 || buf[3] == 0xC2 || buf[3] == 0xC3 || buf[3] == 0xC9 || buf[3] == 0xCA || buf[3] == 0xCB) break; + pos += 2 + (buf[4] << 8) + buf[5]; + if (pos + 12 > len) break; + //fseek(f, pos, SEEK_SET); + f.Seek(pos, SeekOrigin.Begin); + //fread(buf + 2, 1, 12, f); + f.Read(buf, 0, 12); + } + } + } + + // JPEG: (first two bytes of buf are first two bytes of the jpeg file; rest of buf is the DCT frame + if (buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF) + { + var height = (buf[7] << 8) + buf[8]; + var width = (buf[9] << 8) + buf[10]; + + return new Size(width, height); + } + + throw new UnknownImageFormatException(); + } + + private static Size DecodeJpeg2(BinaryReader reader) + { + bool found = false; + bool eof = false; + + while (!found || eof) + { + // read 0xFF and the type + //reader.ReadByte(); + byte type = reader.ReadByte(); + + // get length + int len = 0; + switch (type) + { + // start and end of the image + case 0xD8: + case 0xD9: + len = 0; + break; + + // restart interval + case 0xDD: + len = 2; + break; + + // the next two bytes is the length + default: + int lenHi = reader.ReadByte(); + int lenLo = reader.ReadByte(); + len = (lenHi << 8 | lenLo) - 2; + break; + } + + // EOF? + if (type == 0xD9) + eof = true; + + // process the data + if (len > 0) + { + // read the data + byte[] data = reader.ReadBytes(len); + + // this is what we are looking for + if (type == 0xC0) + { + int width = data[1] << 8 | data[2]; + int height = data[3] << 8 | data[4]; + return new Size(width, height); + } + } + } + + throw new UnknownImageFormatException(); + } + + private static Size DecodeJfif(BinaryReader reader) + { + while (reader.ReadByte() == 0xff) + { + byte marker = reader.ReadByte(); + short chunkLength = ReadLittleEndianInt16(reader); + if (marker == 0xc0) + { + reader.ReadByte(); + int height = ReadLittleEndianInt16(reader); + int width = ReadLittleEndianInt16(reader); + return new Size(width, height); + } + + if (chunkLength < 0) + { + ushort uchunkLength = (ushort)chunkLength; + reader.ReadBytes(uchunkLength - 2); + } + else + { + reader.ReadBytes(chunkLength - 2); + } + } + + throw new UnknownImageFormatException(); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/IO/LocalFileSystem.cs b/src/Libraries/SmartStore.Core/IO/LocalFileSystem.cs index 2e8a08d6a6..3187971ec6 100644 --- a/src/Libraries/SmartStore.Core/IO/LocalFileSystem.cs +++ b/src/Libraries/SmartStore.Core/IO/LocalFileSystem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -137,7 +138,7 @@ static string Fix(string path) : path.TrimStart('/', '\\'); } - public string GetPublicUrl(string path) + public string GetPublicUrl(string path, bool forCloud = false) { return MapPublic(path); } @@ -201,12 +202,26 @@ public IFolder GetFolderForFile(string path) return new LocalFolder(Fix(folderPath), fileInfo.Directory); } - public IEnumerable SearchFiles(string path, string pattern) + public long CountFiles(string path, string pattern, Func predicate, bool deep = true) { - // get relative from absolute path + var files = SearchFiles(path, pattern, deep).AsParallel(); + + if (predicate != null) + { + return files.Count(predicate); + } + else + { + return files.Count(); + } + } + + public IEnumerable SearchFiles(string path, string pattern, bool deep = true) + { + // Get relative from absolute path var index = _storagePath.EmptyNull().Length; - return Directory.EnumerateFiles(MapStorage(path), pattern, SearchOption.AllDirectories) + return Directory.EnumerateFiles(MapStorage(path), pattern, deep ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) .Select(x => x.Substring(index)); } @@ -467,6 +482,7 @@ private class LocalFile : IFile { private readonly string _path; private readonly FileInfo _fileInfo; + private Size? _dimensions; public LocalFile(string path, FileInfo fileInfo) { @@ -479,11 +495,21 @@ public string Path get { return _path; } } + public string Directory + { + get { return _path.Substring(0, _path.Length - Name.Length); } + } + public string Name { get { return _fileInfo.Name; } } + public string Title + { + get { return System.IO.Path.GetFileNameWithoutExtension(_fileInfo.Name); } + } + public long Size { get { return _fileInfo.Length; } @@ -494,11 +520,32 @@ public DateTime LastUpdated get { return _fileInfo.LastWriteTime; } } - public string FileType + public string Extension { get { return _fileInfo.Extension; } } + public Size Dimensions + { + get + { + if (_dimensions == null) + { + try + { + var mime = MimeTypes.MapNameToMimeType(_fileInfo.Name); + _dimensions = ImageHeader.GetDimensions(OpenRead(), mime, false); + } + catch + { + _dimensions = new Size(); + } + } + + return _dimensions.Value; + } + } + public bool Exists { get { return _fileInfo.Exists; } diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs b/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs index c5086b0a63..1693635837 100644 --- a/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs +++ b/src/Libraries/SmartStore.Core/IO/LockFile/LockFile.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using System.IO; using SmartStore.Utilities.Threading; namespace SmartStore.Core.IO @@ -32,9 +33,10 @@ public void Release() { using (_rwLock.GetWriteLock()) { - if (_released || !_folder.FileExists(_path)) + if (_released || !File.Exists(_folder.MapPath(_path))) { // nothing to do, might happen if re-granted or already released + // INFO: VirtualPathProvider caches file existence info, so not very reliable here. return; } diff --git a/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs b/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs index b5c7d5118d..ab48a467b8 100644 --- a/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs +++ b/src/Libraries/SmartStore.Core/IO/LockFile/LockFileManager.cs @@ -1,6 +1,7 @@ using System; using System.Globalization; using System.Threading; +using System.IO; using SmartStore.Utilities.Threading; namespace SmartStore.Core.IO @@ -74,7 +75,8 @@ public bool IsLocked(string path) private bool IsLockedInternal(string path) { - if (_env.TenantFolder.FileExists(path)) + // INFO: VirtualPathProvider caches file existence info, so not very reliable here. + if (File.Exists(_env.TenantFolder.MapPath(path))) { var content = _env.TenantFolder.ReadFile(path); diff --git a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs index d9edddeef5..506821b06f 100644 --- a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs +++ b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs @@ -1,19 +1,767 @@ using System; -using System.Collections.Concurrent; +using System.Linq; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Web; using Microsoft.Win32; +using System.IO; namespace SmartStore.Core.IO { public static class MimeTypes { - private static readonly ConcurrentDictionary _mimeMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly char[] _pathSeparatorChars = new [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, Path.VolumeSeparatorChar }; + private static readonly Dictionary _mimeMap; + private static readonly Dictionary _extMap; - public static string MapNameToMimeType(string fileNameOrExtension) + const string DefaultMimeType = "application/octet-stream"; + + static MimeTypes() + { + // maps both ways, + // extension -> mime type + // and + // mime type -> extension + // + // any mime type on left side not pre-loaded on right side, are added automatically. + // Some mime types can map to multiple extensions, so to get a deterministic mapping, + // add those to the dictionary specifcially + // + // Some added based on http://www.iana.org/assignments/media-types/media-types.xhtml + // which lists mime types, but not extensions + // + + _mimeMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + #region Mime types > extension map + + {"application/fsharp-script", ".fsx"}, + {"application/msaccess", ".adp"}, + {"application/msword", ".doc"}, + {"application/octet-stream", ".bin"}, + {"application/onenote", ".one"}, + {"application/postscript", ".eps"}, + {"application/step", ".step"}, + {"application/vnd.ms-excel", ".xls"}, + {"application/vnd.ms-powerpoint", ".ppt"}, + {"application/vnd.ms-works", ".wks"}, + {"application/vnd.visio", ".vsd"}, + {"application/x-director", ".dir"}, + {"application/x-shockwave-flash", ".swf"}, + {"application/x-x509-ca-cert", ".cer"}, + {"application/x-zip-compressed", ".zip"}, + {"application/xhtml+xml", ".xhtml"}, + {"application/xml", ".xml"}, // anomoly, .xml -> text/xml, but application/xml -> many thingss, but all are xml, so safest is .xml + {"audio/aac", ".AAC"}, + {"audio/aiff", ".aiff"}, + {"audio/basic", ".snd"}, + {"audio/mid", ".midi"}, + {"audio/wav", ".wav"}, + {"audio/x-m4a", ".m4a"}, + {"audio/x-mpegurl", ".m3u"}, + {"audio/x-pn-realaudio", ".ra"}, + {"audio/x-smd", ".smd"}, + {"image/bmp", ".bmp"}, + {"image/jpeg", ".jpg"}, + {"image/pict", ".pic"}, + {"image/png", ".png"}, //Defined in [RFC-2045], [RFC-2048] + {"image/x-png", ".png"}, //See https://www.w3.org/TR/PNG/#A-Media-type :"It is recommended that implementations also recognize the media type "image/x-png"." + {"image/tiff", ".tiff"}, + {"image/x-macpaint", ".mac"}, + {"image/x-quicktime", ".qti"}, + {"message/rfc822", ".eml"}, + {"text/html", ".html"}, + {"text/plain", ".txt"}, + {"text/scriptlet", ".wsc"}, + {"text/xml", ".xml"}, + {"video/3gpp", ".3gp"}, + {"video/3gpp2", ".3gp2"}, + {"video/mp4", ".mp4"}, + {"video/mpeg", ".mpg"}, + {"video/quicktime", ".mov"}, + {"video/vnd.dlna.mpeg-tts", ".m2t"}, + {"video/x-dv", ".dv"}, + {"video/x-la-asf", ".lsf"}, + {"video/x-ms-asf", ".asf"}, + {"x-world/x-vrml", ".xof"}, + + #endregion + }; + + _extMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + #region Extension > mime type map + + {".323", "text/h323"}, + {".3g2", "video/3gpp2"}, + {".3gp", "video/3gpp"}, + {".3gp2", "video/3gpp2"}, + {".3gpp", "video/3gpp"}, + {".7z", "application/x-7z-compressed"}, + {".aa", "audio/audible"}, + {".AAC", "audio/aac"}, + {".aaf", "application/octet-stream"}, + {".aax", "audio/vnd.audible.aax"}, + {".ac3", "audio/ac3"}, + {".aca", "application/octet-stream"}, + {".accda", "application/msaccess.addin"}, + {".accdb", "application/msaccess"}, + {".accdc", "application/msaccess.cab"}, + {".accde", "application/msaccess"}, + {".accdr", "application/msaccess.runtime"}, + {".accdt", "application/msaccess"}, + {".accdw", "application/msaccess.webapplication"}, + {".accft", "application/msaccess.ftemplate"}, + {".acx", "application/internet-property-stream"}, + {".AddIn", "text/xml"}, + {".ade", "application/msaccess"}, + {".adobebridge", "application/x-bridge-url"}, + {".adp", "application/msaccess"}, + {".ADT", "audio/vnd.dlna.adts"}, + {".ADTS", "audio/aac"}, + {".afm", "application/octet-stream"}, + {".ai", "application/postscript"}, + {".aif", "audio/aiff"}, + {".aifc", "audio/aiff"}, + {".aiff", "audio/aiff"}, + {".air", "application/vnd.adobe.air-application-installer-package+zip"}, + {".amc", "application/mpeg"}, + {".anx", "application/annodex"}, + {".apk", "application/vnd.android.package-archive" }, + {".application", "application/x-ms-application"}, + {".art", "image/x-jg"}, + {".asa", "application/xml"}, + {".asax", "application/xml"}, + {".ascx", "application/xml"}, + {".asd", "application/octet-stream"}, + {".asf", "video/x-ms-asf"}, + {".ashx", "application/xml"}, + {".asi", "application/octet-stream"}, + {".asm", "text/plain"}, + {".asmx", "application/xml"}, + {".aspx", "application/xml"}, + {".asr", "video/x-ms-asf"}, + {".asx", "video/x-ms-asf"}, + {".atom", "application/atom+xml"}, + {".au", "audio/basic"}, + {".avi", "video/x-msvideo"}, + {".axa", "audio/annodex"}, + {".axs", "application/olescript"}, + {".axv", "video/annodex"}, + {".bas", "text/plain"}, + {".bcpio", "application/x-bcpio"}, + {".bin", "application/octet-stream"}, + {".bmp", "image/bmp"}, + {".c", "text/plain"}, + {".cab", "application/octet-stream"}, + {".caf", "audio/x-caf"}, + {".calx", "application/vnd.ms-office.calx"}, + {".cat", "application/vnd.ms-pki.seccat"}, + {".cc", "text/plain"}, + {".cd", "text/plain"}, + {".cdda", "audio/aiff"}, + {".cdf", "application/x-cdf"}, + {".cer", "application/x-x509-ca-cert"}, + {".cfg", "text/plain"}, + {".chm", "application/octet-stream"}, + {".class", "application/x-java-applet"}, + {".clp", "application/x-msclip"}, + {".cmd", "text/plain"}, + {".cmx", "image/x-cmx"}, + {".cnf", "text/plain"}, + {".cod", "image/cis-cod"}, + {".config", "application/xml"}, + {".contact", "text/x-ms-contact"}, + {".coverage", "application/xml"}, + {".cpio", "application/x-cpio"}, + {".cpp", "text/plain"}, + {".crd", "application/x-mscardfile"}, + {".crl", "application/pkix-crl"}, + {".crt", "application/x-x509-ca-cert"}, + {".cs", "text/plain"}, + {".csdproj", "text/plain"}, + {".csh", "application/x-csh"}, + {".csproj", "text/plain"}, + {".css", "text/css"}, + {".csv", "text/csv"}, + {".cur", "application/octet-stream"}, + {".cxx", "text/plain"}, + {".dat", "application/octet-stream"}, + {".datasource", "application/xml"}, + {".dbproj", "text/plain"}, + {".dcr", "application/x-director"}, + {".def", "text/plain"}, + {".deploy", "application/octet-stream"}, + {".der", "application/x-x509-ca-cert"}, + {".dgml", "application/xml"}, + {".dib", "image/bmp"}, + {".dif", "video/x-dv"}, + {".dir", "application/x-director"}, + {".disco", "text/xml"}, + {".divx", "video/divx"}, + {".dll", "application/x-msdownload"}, + {".dll.config", "text/xml"}, + {".dlm", "text/dlm"}, + {".doc", "application/msword"}, + {".docm", "application/vnd.ms-word.document.macroEnabled.12"}, + {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, + {".dot", "application/msword"}, + {".dotm", "application/vnd.ms-word.template.macroEnabled.12"}, + {".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, + {".dsp", "application/octet-stream"}, + {".dsw", "text/plain"}, + {".dtd", "text/xml"}, + {".dtsConfig", "text/xml"}, + {".dv", "video/x-dv"}, + {".dvi", "application/x-dvi"}, + {".dwf", "drawing/x-dwf"}, + {".dwg", "application/acad"}, + {".dwp", "application/octet-stream"}, + {".dxf", "application/x-dxf" }, + {".dxr", "application/x-director"}, + {".eml", "message/rfc822"}, + {".emz", "application/octet-stream"}, + {".eot", "application/vnd.ms-fontobject"}, + {".eps", "application/postscript"}, + {".etl", "application/etl"}, + {".etx", "text/x-setext"}, + {".evy", "application/envoy"}, + {".exe", "application/octet-stream"}, + {".exe.config", "text/xml"}, + {".fdf", "application/vnd.fdf"}, + {".fif", "application/fractals"}, + {".filters", "application/xml"}, + {".fla", "application/octet-stream"}, + {".flac", "audio/flac"}, + {".flr", "x-world/x-vrml"}, + {".flv", "video/x-flv"}, + {".fsscript", "application/fsharp-script"}, + {".fsx", "application/fsharp-script"}, + {".generictest", "application/xml"}, + {".gif", "image/gif"}, + {".gpx", "application/gpx+xml"}, + {".group", "text/x-ms-group"}, + {".gsm", "audio/x-gsm"}, + {".gtar", "application/x-gtar"}, + {".gz", "application/x-gzip"}, + {".h", "text/plain"}, + {".hdf", "application/x-hdf"}, + {".hdml", "text/x-hdml"}, + {".hhc", "application/x-oleobject"}, + {".hhk", "application/octet-stream"}, + {".hhp", "application/octet-stream"}, + {".hlp", "application/winhlp"}, + {".hpp", "text/plain"}, + {".hqx", "application/mac-binhex40"}, + {".hta", "application/hta"}, + {".htc", "text/x-component"}, + {".htm", "text/html"}, + {".html", "text/html"}, + {".htt", "text/webviewhtml"}, + {".hxa", "application/xml"}, + {".hxc", "application/xml"}, + {".hxd", "application/octet-stream"}, + {".hxe", "application/xml"}, + {".hxf", "application/xml"}, + {".hxh", "application/octet-stream"}, + {".hxi", "application/octet-stream"}, + {".hxk", "application/xml"}, + {".hxq", "application/octet-stream"}, + {".hxr", "application/octet-stream"}, + {".hxs", "application/octet-stream"}, + {".hxt", "text/html"}, + {".hxv", "application/xml"}, + {".hxw", "application/octet-stream"}, + {".hxx", "text/plain"}, + {".i", "text/plain"}, + {".ico", "image/x-icon"}, + {".ics", "application/octet-stream"}, + {".idl", "text/plain"}, + {".ief", "image/ief"}, + {".iii", "application/x-iphone"}, + {".inc", "text/plain"}, + {".inf", "application/octet-stream"}, + {".ini", "text/plain"}, + {".inl", "text/plain"}, + {".ins", "application/x-internet-signup"}, + {".ipa", "application/x-itunes-ipa"}, + {".ipg", "application/x-itunes-ipg"}, + {".ipproj", "text/plain"}, + {".ipsw", "application/x-itunes-ipsw"}, + {".iqy", "text/x-ms-iqy"}, + {".isp", "application/x-internet-signup"}, + {".ite", "application/x-itunes-ite"}, + {".itlp", "application/x-itunes-itlp"}, + {".itms", "application/x-itunes-itms"}, + {".itpc", "application/x-itunes-itpc"}, + {".IVF", "video/x-ivf"}, + {".jar", "application/java-archive"}, + {".java", "application/octet-stream"}, + {".jck", "application/liquidmotion"}, + {".jcz", "application/liquidmotion"}, + {".jfif", "image/pjpeg"}, + {".jnlp", "application/x-java-jnlp-file"}, + {".jpb", "application/octet-stream"}, + {".jpe", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".jpg", "image/jpeg"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".jsx", "text/jscript"}, + {".jsxbin", "text/plain"}, + {".latex", "application/x-latex"}, + {".library-ms", "application/windows-library+xml"}, + {".lit", "application/x-ms-reader"}, + {".loadtest", "application/xml"}, + {".lpk", "application/octet-stream"}, + {".lsf", "video/x-la-asf"}, + {".lst", "text/plain"}, + {".lsx", "video/x-la-asf"}, + {".lzh", "application/octet-stream"}, + {".m13", "application/x-msmediaview"}, + {".m14", "application/x-msmediaview"}, + {".m1v", "video/mpeg"}, + {".m2t", "video/vnd.dlna.mpeg-tts"}, + {".m2ts", "video/vnd.dlna.mpeg-tts"}, + {".m2v", "video/mpeg"}, + {".m3u", "audio/x-mpegurl"}, + {".m3u8", "audio/x-mpegurl"}, + {".m4a", "audio/m4a"}, + {".m4b", "audio/m4b"}, + {".m4p", "audio/m4p"}, + {".m4r", "audio/x-m4r"}, + {".m4v", "video/x-m4v"}, + {".mac", "image/x-macpaint"}, + {".mak", "text/plain"}, + {".man", "application/x-troff-man"}, + {".manifest", "application/x-ms-manifest"}, + {".map", "text/plain"}, + {".master", "application/xml"}, + {".mbox", "application/mbox"}, + {".mda", "application/msaccess"}, + {".mdb", "application/x-msaccess"}, + {".mde", "application/msaccess"}, + {".mdp", "application/octet-stream"}, + {".me", "application/x-troff-me"}, + {".mfp", "application/x-shockwave-flash"}, + {".mht", "message/rfc822"}, + {".mhtml", "message/rfc822"}, + {".mid", "audio/mid"}, + {".midi", "audio/mid"}, + {".mix", "application/octet-stream"}, + {".mk", "text/plain"}, + {".mk3d", "video/x-matroska-3d"}, + {".mka", "audio/x-matroska"}, + {".mkv", "video/x-matroska"}, + {".mmf", "application/x-smaf"}, + {".mno", "text/xml"}, + {".mny", "application/x-msmoney"}, + {".mod", "video/mpeg"}, + {".mov", "video/quicktime"}, + {".movie", "video/x-sgi-movie"}, + {".mp2", "video/mpeg"}, + {".mp2v", "video/mpeg"}, + {".mp3", "audio/mpeg"}, + {".mp4", "video/mp4"}, + {".mp4v", "video/mp4"}, + {".mpa", "video/mpeg"}, + {".mpe", "video/mpeg"}, + {".mpeg", "video/mpeg"}, + {".mpf", "application/vnd.ms-mediapackage"}, + {".mpg", "video/mpeg"}, + {".mpp", "application/vnd.ms-project"}, + {".mpv2", "video/mpeg"}, + {".mqv", "video/quicktime"}, + {".ms", "application/x-troff-ms"}, + {".msg", "application/vnd.ms-outlook"}, + {".msi", "application/octet-stream"}, + {".mso", "application/octet-stream"}, + {".mts", "video/vnd.dlna.mpeg-tts"}, + {".mtx", "application/xml"}, + {".mvb", "application/x-msmediaview"}, + {".mvc", "application/x-miva-compiled"}, + {".mxp", "application/x-mmxp"}, + {".nc", "application/x-netcdf"}, + {".nsc", "video/x-ms-asf"}, + {".nws", "message/rfc822"}, + {".ocx", "application/octet-stream"}, + {".oda", "application/oda"}, + {".odb", "application/vnd.oasis.opendocument.database"}, + {".odc", "application/vnd.oasis.opendocument.chart"}, + {".odf", "application/vnd.oasis.opendocument.formula"}, + {".odg", "application/vnd.oasis.opendocument.graphics"}, + {".odh", "text/plain"}, + {".odi", "application/vnd.oasis.opendocument.image"}, + {".odl", "text/plain"}, + {".odm", "application/vnd.oasis.opendocument.text-master"}, + {".odp", "application/vnd.oasis.opendocument.presentation"}, + {".ods", "application/vnd.oasis.opendocument.spreadsheet"}, + {".odt", "application/vnd.oasis.opendocument.text"}, + {".oga", "audio/ogg"}, + {".ogg", "audio/ogg"}, + {".ogv", "video/ogg"}, + {".ogx", "application/ogg"}, + {".one", "application/onenote"}, + {".onea", "application/onenote"}, + {".onepkg", "application/onenote"}, + {".onetmp", "application/onenote"}, + {".onetoc", "application/onenote"}, + {".onetoc2", "application/onenote"}, + {".opus", "audio/ogg"}, + {".orderedtest", "application/xml"}, + {".osdx", "application/opensearchdescription+xml"}, + {".otf", "application/font-sfnt"}, + {".otg", "application/vnd.oasis.opendocument.graphics-template"}, + {".oth", "application/vnd.oasis.opendocument.text-web"}, + {".otp", "application/vnd.oasis.opendocument.presentation-template"}, + {".ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, + {".ott", "application/vnd.oasis.opendocument.text-template"}, + {".oxt", "application/vnd.openofficeorg.extension"}, + {".p10", "application/pkcs10"}, + {".p12", "application/x-pkcs12"}, + {".p7b", "application/x-pkcs7-certificates"}, + {".p7c", "application/pkcs7-mime"}, + {".p7m", "application/pkcs7-mime"}, + {".p7r", "application/x-pkcs7-certreqresp"}, + {".p7s", "application/pkcs7-signature"}, + {".pbm", "image/x-portable-bitmap"}, + {".pcast", "application/x-podcast"}, + {".pct", "image/pict"}, + {".pcx", "application/octet-stream"}, + {".pcz", "application/octet-stream"}, + {".pdf", "application/pdf"}, + {".pfb", "application/octet-stream"}, + {".pfm", "application/octet-stream"}, + {".pfx", "application/x-pkcs12"}, + {".pgm", "image/x-portable-graymap"}, + {".pic", "image/pict"}, + {".pict", "image/pict"}, + {".pkgdef", "text/plain"}, + {".pkgundef", "text/plain"}, + {".pko", "application/vnd.ms-pki.pko"}, + {".pls", "audio/scpls"}, + {".pma", "application/x-perfmon"}, + {".pmc", "application/x-perfmon"}, + {".pml", "application/x-perfmon"}, + {".pmr", "application/x-perfmon"}, + {".pmw", "application/x-perfmon"}, + {".png", "image/png"}, + {".pnm", "image/x-portable-anymap"}, + {".pnt", "image/x-macpaint"}, + {".pntg", "image/x-macpaint"}, + {".pnz", "image/png"}, + {".pot", "application/vnd.ms-powerpoint"}, + {".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12"}, + {".potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, + {".ppa", "application/vnd.ms-powerpoint"}, + {".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12"}, + {".ppm", "image/x-portable-pixmap"}, + {".pps", "application/vnd.ms-powerpoint"}, + {".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"}, + {".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, + {".ppt", "application/vnd.ms-powerpoint"}, + {".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12"}, + {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, + {".prf", "application/pics-rules"}, + {".prm", "application/octet-stream"}, + {".prx", "application/octet-stream"}, + {".ps", "application/postscript"}, + {".psc1", "application/PowerShell"}, + {".psd", "application/octet-stream"}, + {".psess", "application/xml"}, + {".psm", "application/octet-stream"}, + {".psp", "application/octet-stream"}, + {".pst", "application/vnd.ms-outlook"}, + {".pub", "application/x-mspublisher"}, + {".pwz", "application/vnd.ms-powerpoint"}, + {".qht", "text/x-html-insertion"}, + {".qhtm", "text/x-html-insertion"}, + {".qt", "video/quicktime"}, + {".qti", "image/x-quicktime"}, + {".qtif", "image/x-quicktime"}, + {".qtl", "application/x-quicktimeplayer"}, + {".qxd", "application/octet-stream"}, + {".ra", "audio/x-pn-realaudio"}, + {".ram", "audio/x-pn-realaudio"}, + {".rar", "application/x-rar-compressed"}, + {".ras", "image/x-cmu-raster"}, + {".rat", "application/rat-file"}, + {".rc", "text/plain"}, + {".rc2", "text/plain"}, + {".rct", "text/plain"}, + {".rdlc", "application/xml"}, + {".reg", "text/plain"}, + {".resx", "application/xml"}, + {".rf", "image/vnd.rn-realflash"}, + {".rgb", "image/x-rgb"}, + {".rgs", "text/plain"}, + {".rm", "application/vnd.rn-realmedia"}, + {".rmi", "audio/mid"}, + {".rmp", "application/vnd.rn-rn_music_package"}, + {".roff", "application/x-troff"}, + {".rpm", "audio/x-pn-realaudio-plugin"}, + {".rqy", "text/x-ms-rqy"}, + {".rtf", "application/rtf"}, + {".rtx", "text/richtext"}, + {".rvt", "application/octet-stream" }, + {".ruleset", "application/xml"}, + {".s", "text/plain"}, + {".safariextz", "application/x-safari-safariextz"}, + {".scd", "application/x-msschedule"}, + {".scr", "text/plain"}, + {".sct", "text/scriptlet"}, + {".sd2", "audio/x-sd2"}, + {".sdp", "application/sdp"}, + {".sea", "application/octet-stream"}, + {".searchConnector-ms", "application/windows-search-connector+xml"}, + {".setpay", "application/set-payment-initiation"}, + {".setreg", "application/set-registration-initiation"}, + {".settings", "application/xml"}, + {".sgimb", "application/x-sgimb"}, + {".sgml", "text/sgml"}, + {".sh", "application/x-sh"}, + {".shar", "application/x-shar"}, + {".shtml", "text/html"}, + {".sit", "application/x-stuffit"}, + {".sitemap", "application/xml"}, + {".skin", "application/xml"}, + {".skp", "application/x-koan" }, + {".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12"}, + {".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, + {".slk", "application/vnd.ms-excel"}, + {".sln", "text/plain"}, + {".slupkg-ms", "application/x-ms-license"}, + {".smd", "audio/x-smd"}, + {".smi", "application/octet-stream"}, + {".smx", "audio/x-smd"}, + {".smz", "audio/x-smd"}, + {".snd", "audio/basic"}, + {".snippet", "application/xml"}, + {".snp", "application/octet-stream"}, + {".sol", "text/plain"}, + {".sor", "text/plain"}, + {".spc", "application/x-pkcs7-certificates"}, + {".spl", "application/futuresplash"}, + {".spx", "audio/ogg"}, + {".src", "application/x-wais-source"}, + {".srf", "text/plain"}, + {".SSISDeploymentManifest", "text/xml"}, + {".ssm", "application/streamingmedia"}, + {".sst", "application/vnd.ms-pki.certstore"}, + {".stl", "application/vnd.ms-pki.stl"}, + {".sv4cpio", "application/x-sv4cpio"}, + {".sv4crc", "application/x-sv4crc"}, + {".svc", "application/xml"}, + {".svg", "image/svg+xml"}, + {".swf", "application/x-shockwave-flash"}, + {".step", "application/step"}, + {".stp", "application/step"}, + {".t", "application/x-troff"}, + {".tar", "application/x-tar"}, + {".tcl", "application/x-tcl"}, + {".testrunconfig", "application/xml"}, + {".testsettings", "application/xml"}, + {".tex", "application/x-tex"}, + {".texi", "application/x-texinfo"}, + {".texinfo", "application/x-texinfo"}, + {".tgz", "application/x-compressed"}, + {".thmx", "application/vnd.ms-officetheme"}, + {".thn", "application/octet-stream"}, + {".tif", "image/tiff"}, + {".tiff", "image/tiff"}, + {".tlh", "text/plain"}, + {".tli", "text/plain"}, + {".toc", "application/octet-stream"}, + {".tr", "application/x-troff"}, + {".trm", "application/x-msterminal"}, + {".trx", "application/xml"}, + {".ts", "video/vnd.dlna.mpeg-tts"}, + {".tsv", "text/tab-separated-values"}, + {".ttf", "application/font-sfnt"}, + {".tts", "video/vnd.dlna.mpeg-tts"}, + {".txt", "text/plain"}, + {".u32", "application/octet-stream"}, + {".uls", "text/iuls"}, + {".user", "text/plain"}, + {".ustar", "application/x-ustar"}, + {".vb", "text/plain"}, + {".vbdproj", "text/plain"}, + {".vbk", "video/mpeg"}, + {".vbproj", "text/plain"}, + {".vbs", "text/vbscript"}, + {".vcf", "text/x-vcard"}, + {".vcproj", "application/xml"}, + {".vcs", "text/plain"}, + {".vcxproj", "application/xml"}, + {".vddproj", "text/plain"}, + {".vdp", "text/plain"}, + {".vdproj", "text/plain"}, + {".vdx", "application/vnd.ms-visio.viewer"}, + {".vml", "text/xml"}, + {".vscontent", "application/xml"}, + {".vsct", "text/xml"}, + {".vsd", "application/vnd.visio"}, + {".vsi", "application/ms-vsi"}, + {".vsix", "application/vsix"}, + {".vsixlangpack", "text/xml"}, + {".vsixmanifest", "text/xml"}, + {".vsmdi", "application/xml"}, + {".vspscc", "text/plain"}, + {".vss", "application/vnd.visio"}, + {".vsscc", "text/plain"}, + {".vssettings", "text/xml"}, + {".vssscc", "text/plain"}, + {".vst", "application/vnd.visio"}, + {".vstemplate", "text/xml"}, + {".vsto", "application/x-ms-vsto"}, + {".vsw", "application/vnd.visio"}, + {".vsx", "application/vnd.visio"}, + {".vtt", "text/vtt"}, + {".vtx", "application/vnd.visio"}, + {".wasm", "application/wasm"}, + {".wav", "audio/wav"}, + {".wave", "audio/wav"}, + {".wax", "audio/x-ms-wax"}, + {".wbk", "application/msword"}, + {".wbmp", "image/vnd.wap.wbmp"}, + {".wcm", "application/vnd.ms-works"}, + {".wdb", "application/vnd.ms-works"}, + {".wdp", "image/vnd.ms-photo"}, + {".webarchive", "application/x-safari-webarchive"}, + {".webm", "video/webm"}, + {".webp", "image/webp"}, /* https://en.wikipedia.org/wiki/WebP */ + {".webtest", "application/xml"}, + {".wiq", "application/xml"}, + {".wiz", "application/msword"}, + {".wks", "application/vnd.ms-works"}, + {".WLMP", "application/wlmoviemaker"}, + {".wlpginstall", "application/x-wlpg-detect"}, + {".wlpginstall3", "application/x-wlpg3-detect"}, + {".wm", "video/x-ms-wm"}, + {".wma", "audio/x-ms-wma"}, + {".wmd", "application/x-ms-wmd"}, + {".wmf", "application/x-msmetafile"}, + {".wml", "text/vnd.wap.wml"}, + {".wmlc", "application/vnd.wap.wmlc"}, + {".wmls", "text/vnd.wap.wmlscript"}, + {".wmlsc", "application/vnd.wap.wmlscriptc"}, + {".wmp", "video/x-ms-wmp"}, + {".wmv", "video/x-ms-wmv"}, + {".wmx", "video/x-ms-wmx"}, + {".wmz", "application/x-ms-wmz"}, + {".woff", "application/font-woff"}, + {".woff2", "application/font-woff2"}, + {".wpl", "application/vnd.ms-wpl"}, + {".wps", "application/vnd.ms-works"}, + {".wri", "application/x-mswrite"}, + {".wrl", "x-world/x-vrml"}, + {".wrz", "x-world/x-vrml"}, + {".wsc", "text/scriptlet"}, + {".wsdl", "text/xml"}, + {".wvx", "video/x-ms-wvx"}, + {".x", "application/directx"}, + {".xaf", "x-world/x-vrml"}, + {".xaml", "application/xaml+xml"}, + {".xap", "application/x-silverlight-app"}, + {".xbap", "application/x-ms-xbap"}, + {".xbm", "image/x-xbitmap"}, + {".xdr", "text/plain"}, + {".xht", "application/xhtml+xml"}, + {".xhtml", "application/xhtml+xml"}, + {".xla", "application/vnd.ms-excel"}, + {".xlam", "application/vnd.ms-excel.addin.macroEnabled.12"}, + {".xlc", "application/vnd.ms-excel"}, + {".xld", "application/vnd.ms-excel"}, + {".xlk", "application/vnd.ms-excel"}, + {".xll", "application/vnd.ms-excel"}, + {".xlm", "application/vnd.ms-excel"}, + {".xls", "application/vnd.ms-excel"}, + {".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12"}, + {".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12"}, + {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, + {".xlt", "application/vnd.ms-excel"}, + {".xltm", "application/vnd.ms-excel.template.macroEnabled.12"}, + {".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, + {".xlw", "application/vnd.ms-excel"}, + {".xml", "text/xml"}, + {".xmp", "application/octet-stream" }, + {".xmta", "application/xml"}, + {".xof", "x-world/x-vrml"}, + {".XOML", "text/plain"}, + {".xpm", "image/x-xpixmap"}, + {".xps", "application/vnd.ms-xpsdocument"}, + {".xrm-ms", "text/xml"}, + {".xsc", "application/xml"}, + {".xsd", "text/xml"}, + {".xsf", "text/xml"}, + {".xsl", "text/xml"}, + {".xslt", "text/xml"}, + {".xsn", "application/octet-stream"}, + {".xss", "application/xml"}, + {".xspf", "application/xspf+xml"}, + {".xtp", "application/octet-stream"}, + {".xwd", "image/x-xwindowdump"}, + {".z", "application/x-compress"}, + {".zip", "application/zip"}, + + #endregion + }; + + // Shuffle entries + ShuffleMap(_mimeMap, _extMap); + ShuffleMap(_extMap, _mimeMap); + + // Complement maps with entries from registry + try + { + using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type", false)) + { + if (key != null) + { + var subKeyNames = key.GetSubKeyNames(); + foreach (var contentType in subKeyNames) + { + if (!_mimeMap.ContainsKey(contentType)) + { + // Our map does not contain this entry. We gonna add it. + using (var subKey = key.OpenSubKey(contentType, false)) + { + object extension = subKey.GetValue("Extension", null); + if (extension != null) + { + // MimeType > Extension + var ext = extension.ToString(); + _mimeMap.Add(contentType, ext); + + // Vice versa, Extension > MimeType + if (!_extMap.ContainsKey(ext)) + { + _extMap.Add(ext, contentType); + } + } + } + } + } + } + } + } + catch { } + } + + public static string MapNameToMimeType(string fileNameOrExtension) { - return MimeMapping.GetMimeMapping(fileNameOrExtension); + var fileName = GetFileName(fileNameOrExtension); + + for (int i = 0; i < fileName.Length; i++) + { + if ((fileName[i] == '.') && _extMap.TryGetValue(fileName.Substring(i), out var str)) + { + return str; + } + } + + + return DefaultMimeType; } /// @@ -27,37 +775,66 @@ public static string MapMimeTypeToExtension(string mimeType) if (mimeType.IsEmpty()) return null; - return _mimeMap.GetOrAdd(mimeType, k => { - string result; + if (_mimeMap.TryGetValue(mimeType, out var extension)) + { + return extension.TrimStart('.'); + } - try - { - using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false)) - { - object value = key != null ? key.GetValue("Extension", null) : null; - result = value != null ? value.ToString().Trim('.') : null; - } - } - catch + return null; + + //return _mimeMap.GetOrAdd(mimeType, k => { + // string result; + + // try + // { + // using (var key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false)) + // { + // object value = key != null ? key.GetValue("Extension", null) : null; + // result = value != null ? value.ToString().Trim('.') : null; + // } + // } + // catch + // { + // string[] parts = mimeType.Split('/'); + // result = parts[parts.Length - 1]; + // switch (result) + // { + // case "pjpeg": + // result = "jpg"; + // break; + // case "x-png": + // result = "png"; + // break; + // case "x-icon": + // result = "ico"; + // break; + // } + // } + + // return result; + //}); + } + + private static void ShuffleMap(Dictionary source, Dictionary target) + { + foreach (var kvp in source) + { + if (!target.ContainsKey(kvp.Value)) { - string[] parts = mimeType.Split('/'); - result = parts[parts.Length - 1]; - switch (result) - { - case "pjpeg": - result = "jpg"; - break; - case "x-png": - result = "png"; - break; - case "x-icon": - result = "ico"; - break; - } + target.Add(kvp.Value, kvp.Key); } + } + } - return result; - }); - } - } + private static string GetFileName(string path) + { + var startIndex = path.LastIndexOfAny(_pathSeparatorChars); + if (startIndex < 0) + { + return path; + } + + return path.Substring(startIndex); + } + } } diff --git a/src/Libraries/SmartStore.Core/IO/SymLink/FileSystemInfoExtensions.cs b/src/Libraries/SmartStore.Core/IO/SymLink/FileSystemInfoExtensions.cs new file mode 100644 index 0000000000..2c7ed00c92 --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/SymLink/FileSystemInfoExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using SmartStore.Core.IO; + +namespace SmartStore +{ + public static class FileSystemInfoExtensions + { + /// + /// Determines whether this file system entry is a symbolic link. + /// + /// The directory or file in question. + /// true if the entry is a symbolic link, false otherwise. + public static bool IsSymbolicLink(this FileSystemInfo fsi) + { + return SymbolicLink.IsSymbolicLink(fsi); + } + + /// + /// Returns the full path to the target of a symbolic link or mount. + /// + /// The symbolic link in question. + /// The path to the target. + public static string GetFinalPathName(this FileSystemInfo fsi) + { + return SymbolicLink.GetFinalPathName(fsi.FullName); + } + } +} diff --git a/src/Libraries/SmartStore.Core/IO/SymLink/SymbolicLink.cs b/src/Libraries/SmartStore.Core/IO/SymLink/SymbolicLink.cs new file mode 100644 index 0000000000..09567cf1eb --- /dev/null +++ b/src/Libraries/SmartStore.Core/IO/SymLink/SymbolicLink.cs @@ -0,0 +1,103 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace SmartStore.Core.IO +{ + internal static class SymbolicLink + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr CreateFile( + [MarshalAs(UnmanagedType.LPTStr)] string filename, + [MarshalAs(UnmanagedType.U4)] uint access, + [MarshalAs(UnmanagedType.U4)] FileShare share, + IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero + [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, + [MarshalAs(UnmanagedType.U4)] uint flagsAndAttributes, + IntPtr templateFile); + + //[DllImport("kernel32.dll", EntryPoint = "CreateSymbolicLinkW", CharSet = CharSet.Unicode, SetLastError = true)] + //private static extern bool CreateSymbolicLink( + // [In] string lpSymlinkFileName, + // [In] string lpTargetFileName, + // [In] int dwFlags); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern uint GetFinalPathNameByHandle( + IntPtr hFile, + [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, + uint cchFilePath, + uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool CloseHandle(IntPtr hObject); + + private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + private const uint FILE_READ_EA = 0x0008; + + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + public static bool IsSymbolicLink(FileSystemInfo fsi) + { + Guard.NotNull(fsi, nameof(fsi)); + + if (!fsi.Exists) + return false; + + if (fsi.Attributes.HasFlag(FileAttributes.ReparsePoint)) + { + var target = GetFinalPathName(fsi.FullName); + if (target.HasValue()) + { + return !string.Equals(target.TrimEnd('\\'), fsi.FullName.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase); + } + } + + return false; + } + + public static string GetFinalPathName(string path) + { + Guard.NotEmpty(path, nameof(path)); + + var h = CreateFile(path, + FILE_READ_EA, + FileShare.ReadWrite | FileShare.Delete, + IntPtr.Zero, + FileMode.Open, + FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero); + + if (h == INVALID_HANDLE_VALUE) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + try + { + var sb = new StringBuilder(1024); + var res = GetFinalPathNameByHandle(h, sb, 1024, 0); + if (res == 0) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + var result = sb.ToString(); + + if (result.Length >= 4 && result.StartsWith(@"\\?\")) + { + return result.Substring(4);// remove "\\?\" + } + + return result; + } + finally + { + CloseHandle(h); + } + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs index c173ed8549..2d78ea0447 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/DefaultVirtualPathProvider.cs @@ -6,7 +6,6 @@ using System.Web.Caching; using SmartStore.Utilities; using System.Web; -using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Logging; namespace SmartStore.Core.IO diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs index 7db7ae9f7c..8b892dedea 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Logging; namespace SmartStore.Core.IO diff --git a/src/Libraries/SmartStore.Core/IPageable.cs b/src/Libraries/SmartStore.Core/IPageable.cs index ef3ce500a2..6689becf09 100644 --- a/src/Libraries/SmartStore.Core/IPageable.cs +++ b/src/Libraries/SmartStore.Core/IPageable.cs @@ -82,10 +82,17 @@ public interface IPageable : IEnumerable public interface IPagedList : IPageable, IList { /// - /// Return the original query without any paging applied + /// Gets underlying query without any paging applied /// IQueryable SourceQuery { get; } + /// + /// Allows modification of the underlying query before it is executed. + /// + /// The alteration function. The underlying query is passed, the modified query should be returned. + /// The current instance for chaining + IPagedList AlterQuery(Func, IQueryable> alterer); + /// /// Applies the initial paging arguments to the passed query /// diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs index 69a9040025..153405382b 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs @@ -175,7 +175,6 @@ protected void RegisterSignatureProperty(string propertyName) [Serializable] public abstract class ComparableObject : ComparableObject, IEquatable { - /// /// Adds an extra property to the type specific signature properties list. /// @@ -198,7 +197,6 @@ public virtual bool Equals(T other) return base.Equals(other); } - } } diff --git a/src/Libraries/SmartStore.Core/Localization/ILocalizationFileResolver.cs b/src/Libraries/SmartStore.Core/Localization/ILocalizationFileResolver.cs new file mode 100644 index 0000000000..e734c92653 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Localization/ILocalizationFileResolver.cs @@ -0,0 +1,44 @@ +using System; + +namespace SmartStore.Core.Localization +{ + /// + /// Responsible for finding a localization file for client scripts + /// + public interface ILocalizationFileResolver + { + /// + /// Tries to find a matching localization file for a given culture in the following order + /// (assuming is 'de-DE', is 'lang-*.js' and is 'en-US'): + /// + /// Exact match > lang-de-DE.js + /// Neutral culture > lang-de.js + /// Any region for language > lang-de-CH.js + /// Exact match for fallback culture > lang-en-US.js + /// Neutral fallback culture > lang-en.js + /// Any region for fallback language > lang-en-GB.js + /// + /// + /// The ISO culture code to get a localization file for, e.g. 'de-DE' + /// The virtual path to search in + /// The pattern to match, e.g. 'lang-*.js'. The wildcard char MUST exist. + /// + /// Whether caching should be enabled. If false, no attempt is made to read from cache, nor writing the result to the cache. + /// Cache duration is 24 hours. Automatic eviction on file change is NOT performed. + /// + /// Optional. + /// Result + LocalizationFileResolveResult Resolve( + string culture, + string virtualPath, + string pattern, + bool cache = true, + string fallbackCulture = "en"); + } + + public class LocalizationFileResolveResult + { + public string Culture { get; set; } + public string VirtualPath { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Localization/LocalizationFileResolver.cs b/src/Libraries/SmartStore.Core/Localization/LocalizationFileResolver.cs new file mode 100644 index 0000000000..fc02435c1e --- /dev/null +++ b/src/Libraries/SmartStore.Core/Localization/LocalizationFileResolver.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using SmartStore.Core.Caching; +using SmartStore.Utilities; + +namespace SmartStore.Core.Localization +{ + public class LocalizationFileResolver : ILocalizationFileResolver + { + private readonly ICacheManager _cache; + + public LocalizationFileResolver(ICacheManager cache) + { + _cache = cache; + } + + public LocalizationFileResolveResult Resolve( + string culture, + string virtualPath, + string pattern, + bool cache = true, + string fallbackCulture = "en") + { + Guard.NotEmpty(culture, nameof(culture)); + Guard.NotEmpty(virtualPath, nameof(virtualPath)); + Guard.NotEmpty(pattern, nameof(pattern)); + + if (pattern.IndexOf('*') < 0) + { + throw new ArgumentException("The pattern must contain a wildcard char for substitution, e.g. 'lang-*.js'.", nameof(pattern)); + } + + virtualPath = FixPath(virtualPath); + var cacheKey = "core:locfile:" + virtualPath.ToLower() + pattern + "/" + culture; + string result = null; + + if (cache && _cache.Contains(cacheKey)) + { + result = _cache.Get(cacheKey); + return result != null ? CreateResult(result, virtualPath, pattern) : null; + } + + if (!LocalizationHelper.IsValidCultureCode(culture)) + { + throw new ArgumentException($"'{culture}' is not a valid culture code.", nameof(culture)); + } + + var ci = CultureInfo.GetCultureInfo(culture); + var directory = new DirectoryInfo(CommonHelper.MapPath(virtualPath, false)); + + if (!directory.Exists) + { + throw new DirectoryNotFoundException($"Path '{virtualPath}' does not exist."); + } + + // 1: Match passed culture + result = ResolveMatchingFile(ci, directory, pattern); + + if (result == null && fallbackCulture.HasValue() && culture != fallbackCulture) + { + if (!LocalizationHelper.IsValidCultureCode(fallbackCulture)) + { + throw new ArgumentException($"'{culture}' is not a valid culture code.", nameof(fallbackCulture)); + } + + // 2: Match fallback culture + ci = CultureInfo.GetCultureInfo(fallbackCulture); + result = ResolveMatchingFile(ci, directory, pattern); + } + + if (cache) + { + _cache.Put(cacheKey, result, TimeSpan.FromHours(24)); + } + + if (result.HasValue()) + { + return CreateResult(result, virtualPath, pattern); + } + + return null; + } + + private string ResolveMatchingFile(CultureInfo ci, DirectoryInfo directory, string pattern) + { + string result = null; + + // 1: Exact match + // ----------------------------------------------------- + var fileName = pattern.Replace("*", ci.Name); + if (File.Exists(Path.Combine(directory.FullName, fileName))) + { + result = ci.Name; + } + + // 2: Match neutral culture, e.g. de-DE > de + // ----------------------------------------------------- + if (result == null && !ci.IsNeutralCulture && ci.Parent != null) + { + ci = ci.Parent; + fileName = pattern.Replace("*", ci.Name); + if (File.Exists(Path.Combine(directory.FullName, fileName))) + { + result = ci.Name; + } + } + + // 2: Match any region, e.g. de-DE > de-CH + // ----------------------------------------------------- + if (result == null && ci.IsNeutralCulture) + { + // Convert pattern to Regex: "lang-*.js" > "^lang.(.+?).js$" + var rgPattern = "^" + pattern.Replace("*", @"(.+?)") + "$"; + var rgFileName = new Regex(rgPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); + + foreach (var fi in directory.EnumerateFiles(pattern.Replace("*", ci.Name + "-*"), SearchOption.TopDirectoryOnly)) + { + var culture = rgFileName.Match(fi.Name).Groups[1].Value; + if (LocalizationHelper.IsValidCultureCode(culture)) + { + result = culture; + break; + } + } + } + + return result; + } + + private string FixPath(string virtualPath) + { + return VirtualPathUtility.ToAppRelative(virtualPath).EnsureEndsWith("/"); + } + + private LocalizationFileResolveResult CreateResult(string culture, string virtualPath, string pattern) + { + var fileName = pattern.Replace("*", culture); + return new LocalizationFileResolveResult + { + Culture = culture, + VirtualPath = VirtualPathUtility.ToAbsolute(virtualPath + fileName) + }; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Money.cs b/src/Libraries/SmartStore.Core/Money.cs new file mode 100644 index 0000000000..e4bdf4a24f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Money.cs @@ -0,0 +1,477 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; +using SmartStore.Core.Domain.Directory; + +namespace SmartStore +{ + public class Money : IConvertible, IFormattable, IComparable, IComparable, IEquatable + { + public Money(Currency currency) + : this(0m, currency) + { + } + + public Money(float amount, Currency currency) + : this((decimal)amount, currency, false) + { + } + + public Money(double amount, Currency currency) + : this((decimal)amount, currency, false) + { + } + + public Money(decimal amount, Currency currency) + : this(amount, currency, false) + { + } + + public Money(decimal amount, Currency currency, bool hideCurrency) + { + Guard.NotNull(currency, nameof(currency)); + + Amount = amount; + Currency = currency; + HideCurrency = hideCurrency; + } + + [IgnoreDataMember] + public bool HideCurrency + { + get; + internal set; + } + + [IgnoreDataMember] + public Currency Currency + { + get; + internal set; + } + + /// + /// Gets the number of decimal digits for the associated currency. + /// + public int DecimalDigits + { + get => string.Equals(Currency?.CurrencyCode, "btc", StringComparison.OrdinalIgnoreCase) ? 8 : Currency.NumberFormat.CurrencyDecimalDigits; + } + + /// + /// The internal unrounded raw amount + /// + public decimal Amount + { + get; + set; + } + + /// + /// Rounds the amount to the number of significant decimal digits + /// of the associated currency using MidpointRounding.AwayFromZero. + /// + public decimal RoundedAmount + { + get + { + return decimal.Round(Amount, DecimalDigits, MidpointRounding.AwayFromZero); + } + } + + /// + /// Truncates the amount to the number of significant decimal digits + /// of the associated currency. + /// + public decimal TruncatedAmount + { + get => (decimal)((long)Math.Truncate(Amount * DecimalDigits)) / DecimalDigits; + } + + /// + /// The formatted amount + /// + public string Formatted + { + get => ToString(true, false); + } + + private static void GuardCurrenciesAreEqual(Money a, Money b) + { + if (a.Currency != b.Currency) + throw new InvalidOperationException("Cannot operate on money values with different currencies."); + } + + #region Compare + + public override int GetHashCode() + { + if (Amount == 0) + return 0; + + return Amount.GetHashCode() ^ Currency.GetHashCode(); + } + + public int CompareTo(Money other) + { + return ((IComparable)this).CompareTo(other); + } + + int IComparable.CompareTo(object obj) + { + if (obj == null || !(obj is Money)) + return 1; + + Money other = (Money)obj; + + if (this.Amount == other.Amount) + return 0; + if (this.Amount < other.Amount) + return -1; + + return 1; + } + + public override bool Equals(object obj) + { + return Equals(obj as Money); + } + + bool IEquatable.Equals(Money other) + { + if (other == null) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (other.Amount == 0 && this.Amount == 0) + return true; + + return other.Amount == this.Amount && other.Currency == this.Currency; + } + + public static bool operator ==(Money a, Money b) => a.Equals(b); + public static bool operator !=(Money a, Money b) => !a.Equals(b); + + public static bool operator >(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return a.Amount > b.Amount; + } + + public static bool operator <(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return a.Amount < b.Amount; + } + + public static bool operator <=(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return a.Amount <= b.Amount; + + } + + public static bool operator >=(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return a.Amount >= b.Amount; + + } + + public static bool operator ==(Money a, int b) => a.Amount == b; + public static bool operator !=(Money a, int b) => a.Amount != b; + public static bool operator >(Money a, int b) => a.Amount > b; + public static bool operator <(Money a, int b) => a.Amount < b; + public static bool operator <=(Money a, int b) => a.Amount <= b; + public static bool operator >=(Money a, int b) => a.Amount >= b; + + public static bool operator ==(Money a, float b) => a.Amount == (decimal)b; + public static bool operator !=(Money a, float b) => a.Amount != (decimal)b; + public static bool operator >(Money a, float b) => a.Amount > (decimal)b; + public static bool operator <(Money a, float b) => a.Amount < (decimal)b; + public static bool operator <=(Money a, float b) => a.Amount <= (decimal)b; + public static bool operator >=(Money a, float b) => a.Amount >= (decimal)b; + + public static bool operator ==(Money a, double b) => a.Amount == (decimal)b; + public static bool operator !=(Money a, double b) => a.Amount != (decimal)b; + public static bool operator >(Money a, double b) => a.Amount > (decimal)b; + public static bool operator <(Money a, double b) => a.Amount < (decimal)b; + public static bool operator <=(Money a, double b) => a.Amount <= (decimal)b; + public static bool operator >=(Money a, double b) => a.Amount >= (decimal)b; + + public static bool operator ==(Money a, decimal b) => a.Amount == b; + public static bool operator !=(Money a, decimal b) => a.Amount != b; + public static bool operator >(Money a, decimal b) => a.Amount > b; + public static bool operator <(Money a, decimal b) => a.Amount < b; + public static bool operator <=(Money a, decimal b) => a.Amount <= b; + public static bool operator >=(Money a, decimal b) => a.Amount >= b; + + #endregion + + #region Format + + string IFormattable.ToString(string format, IFormatProvider formatProvider) + { + return this.ToString(!HideCurrency, false); + } + + string IConvertible.ToString(IFormatProvider provider) + { + return this.ToString(!HideCurrency, false); + } + + public override string ToString() + { + return this.ToString(!HideCurrency, false); + } + + public string ToString(bool showCurrency) + { + return this.ToString(showCurrency, false); + } + + public string ToString(bool showCurrency, bool useISOCodeAsSymbol) + { + var fmt = Currency.NumberFormat; + + if (Currency.CustomFormatting.HasValue()) + { + return RoundedAmount.ToString(Currency.CustomFormatting, fmt); + } + else + { + if (!showCurrency || useISOCodeAsSymbol) + { + fmt = (NumberFormatInfo)Currency.NumberFormat.Clone(); + fmt.CurrencySymbol = !showCurrency ? "" : Currency.CurrencyCode; + } + + return RoundedAmount.ToString("C", fmt); + } + } + + #endregion + + #region Convert + + // For truthy checks in templating + public static explicit operator bool(Money money) => money.Amount != 0; + public static explicit operator string(Money money) => money.ToString(true, false); + public static explicit operator byte(Money money) => System.Convert.ToByte(money.Amount); + public static explicit operator decimal(Money money) => money.Amount; + public static explicit operator double(Money money) => System.Convert.ToDouble(money.Amount); + public static explicit operator float(Money money) => System.Convert.ToSingle(money.Amount); + public static explicit operator int(Money money) => System.Convert.ToInt32(money.Amount); + public static explicit operator long(Money money) => System.Convert.ToInt64(money.Amount); + public static explicit operator sbyte(Money money) => System.Convert.ToSByte(money.Amount); + public static explicit operator short(Money money) => System.Convert.ToInt16(money.Amount); + public static explicit operator ushort(Money money) => System.Convert.ToUInt16(money.Amount); + public static explicit operator uint(Money money) => System.Convert.ToUInt32(money.Amount); + public static explicit operator ulong(Money money) => System.Convert.ToUInt64(money.Amount); + + TypeCode IConvertible.GetTypeCode() => TypeCode.Decimal; + object IConvertible.ToType(Type conversionType, IFormatProvider provider) => System.Convert.ChangeType(this.Amount, conversionType, provider); + bool IConvertible.ToBoolean(IFormatProvider provider) => Amount != 0; + char IConvertible.ToChar(IFormatProvider provider) => throw Error.InvalidCast(typeof(Money), typeof(char)); + DateTime IConvertible.ToDateTime(IFormatProvider provider) => throw Error.InvalidCast(typeof(Money), typeof(DateTime)); + byte IConvertible.ToByte(IFormatProvider provider) => (byte)this.Amount; + decimal IConvertible.ToDecimal(IFormatProvider provider) => this.Amount; + double IConvertible.ToDouble(IFormatProvider provider) => (double)this.Amount; + short IConvertible.ToInt16(IFormatProvider provider) => (short)this.Amount; + int IConvertible.ToInt32(IFormatProvider provider) => (int)this.Amount; + long IConvertible.ToInt64(IFormatProvider provider) => (long)this.Amount; + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)this.Amount; + float IConvertible.ToSingle(IFormatProvider provider) => (float)this.Amount; + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)this.Amount; + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)this.Amount; + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)this.Amount; + + #endregion + + #region Add + + public static Money operator ++(Money a) + { + a.Amount++; + return a; + } + + public static Money operator +(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return new Money(a.Amount + b.Amount, a.Currency); + } + + public static Money operator +(Money a, int b) => a + (decimal)b; + public static Money operator +(Money a, float b) => a + (decimal)b; + public static Money operator +(Money a, double b) => a + (decimal)b; + public static Money operator +(Money a, decimal b) => new Money(a.Amount + b, a.Currency); + + #endregion + + #region Substract + + public static Money operator --(Money a) + { + a.Amount--; + return a; + } + + public static Money operator -(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return new Money(a.Amount - b.Amount, a.Currency); + } + + public static Money operator -(Money a, int b) => a + (decimal)b; + public static Money operator -(Money a, float b) => a + (decimal)b; + public static Money operator -(Money a, double b) => a + (decimal)b; + public static Money operator -(Money a, decimal b) => new Money(a.Amount - b, a.Currency); + + #endregion + + #region Multiply + + public static Money operator *(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return new Money(a.Amount - b.Amount, a.Currency); + } + + public static Money operator *(Money a, int b) => a * (decimal)b; + public static Money operator *(Money a, float b) => a * (decimal)b; + public static Money operator *(Money a, double b) => a * (decimal)b; + public static Money operator *(Money a, decimal b) => new Money(a.Amount * b, a.Currency); + + #endregion + + #region Divide + + public static Money operator /(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + return new Money(a.Amount / b.Amount, a.Currency); + } + + public static Money operator /(Money a, int b) => a / (decimal)b; + public static Money operator /(Money a, float b) => a / (decimal)b; + public static Money operator /(Money a, double b) => a / (decimal)b; + public static Money operator /(Money a, decimal b) => new Money(a.Amount / b, a.Currency); + + #endregion + + #region Exchange & Math + + ///// + ///// Rounds the amount if enabled for the currency or if is true + ///// + ///// Round also if disabled for the currency + ///// A new instance with the rounded amount + //public Money Round(bool force = false) + //{ + + //} + + public Money ConvertTo(Currency toCurrency) + { + if (Currency == toCurrency) + return this; + + return new Money((Amount * Currency.Rate) / toCurrency.Rate, toCurrency); + } + + /// + /// Evenly distributes the amount over n parts, resolving remainders that occur due to rounding + /// errors, thereby garuanteeing the postcondition: result->sum(r|r.amount) = this.amount and + /// x elements in result are greater than at least one of the other elements, where x = amount mod n. + /// + /// Number of parts over which the amount is to be distibuted. + /// Array with distributed Money amounts. + public Money[] Allocate(int n) + { + var cents = Math.Pow(10, DecimalDigits); + var lowResult = ((long)Math.Truncate((double)Amount / n * cents)) / cents; + var highResult = lowResult + 1.0d / cents; + var results = new Money[n]; + var remainder = (int)(((double)Amount * cents) % n); + + for (var i = 0; i < remainder; i++) + results[i] = new Money((decimal)highResult, Currency); + + for (var i = remainder; i < n; i++) + results[i] = new Money((decimal)lowResult, Currency); + + return results; + } + + /// + /// Gets the ratio of one money to another. + /// + /// The numerator of the operation. + /// The denominator of the operation. + /// A decimal from 0.0 to 1.0 of the ratio between the two money values. + public static decimal GetRatio(Money numerator, Money denominator) + { + if (numerator == 0) + return 0; + + if (denominator == 0) + throw new DivideByZeroException("Attempted to divide by zero!"); + + GuardCurrenciesAreEqual(numerator, denominator); + + return numerator.Amount / denominator.Amount; + } + + /// + /// Gets the smallest money, given the two values. + /// + /// The first money to compare. + /// The second money to compare. + /// The smallest money value of the arguments. + public static Money Min(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + + if (a == b) + return a; + else if (a > b) + return b; + else + return a; + } + + /// + /// Gets the largest money, given the two values. + /// + /// The first money to compare. + /// The second money to compare. + /// The largest money value of the arguments. + public static Money Max(Money a, Money b) + { + GuardCurrenciesAreEqual(a, b); + + if (a == b) + return a; + else if (a > b) + return a; + else + return b; + } + + /// + /// Gets the absolute value of the . + /// + /// The value of money to convert. + /// The money value as an absolute value. + public static Money Abs(Money value) + { + return new Money(Math.Abs(value.Amount), value.Currency); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Packaging/PackagingUtils.cs b/src/Libraries/SmartStore.Core/Packaging/PackagingUtils.cs index 91657a7900..0d50f65851 100644 --- a/src/Libraries/SmartStore.Core/Packaging/PackagingUtils.cs +++ b/src/Libraries/SmartStore.Core/Packaging/PackagingUtils.cs @@ -9,10 +9,8 @@ namespace SmartStore.Core.Packaging { - public static class PackagingUtils - { - + { public static string GetExtensionPrefix(string extensionType) { return string.Format("SmartStore.{0}.", extensionType); @@ -23,8 +21,6 @@ public static string BuildPackageId(string extensionName, string extensionType) return GetExtensionPrefix(extensionType) + extensionName; } - - internal static bool IsTheme(this IPackage package) { return IsTheme(package.Id); diff --git a/src/Libraries/SmartStore.Core/PagedList`T.cs b/src/Libraries/SmartStore.Core/PagedList`T.cs index 1614557a1e..dcebc38642 100644 --- a/src/Libraries/SmartStore.Core/PagedList`T.cs +++ b/src/Libraries/SmartStore.Core/PagedList`T.cs @@ -75,6 +75,14 @@ public IQueryable SourceQuery get { return _query; } } + public IPagedList AlterQuery(Func, IQueryable> alterer) + { + var result = alterer?.Invoke(_query); + _query = result ?? throw new InvalidOperationException("The '{0}' delegate must not return NULL.".FormatInvariant(nameof(alterer))); + + return this; + } + public IQueryable ApplyPaging(IQueryable query) { if (_pageIndex == 0 && _pageSize == int.MaxValue) diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs index 240a788883..35b2b3e8c9 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs @@ -42,11 +42,16 @@ public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFi /// public string PhysicalPath { get; set; } - /// - /// Gets the file name of the brand image (without path) - /// or an empty string if no image is specified - /// - public string BrandImageFileName + /// + /// The virtual path of the runtime plugin + /// + public string VirtualPath { get; set; } + + /// + /// Gets the file name of the brand image (without path) + /// or an empty string if no image is specified + /// + public string BrandImageFileName { get { diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs index 072b362276..6a95a30aae 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs @@ -15,6 +15,7 @@ using SmartStore.Core.Packaging; using SmartStore.Utilities; using SmartStore.Utilities.Threading; +using SmartStore.Core.Data; // Contributor: Umbraco (http://www.umbraco.com). Thanks a lot! // SEE THIS POST for full details of what this does @@ -185,7 +186,7 @@ public static void Initialize() } } - if (dirty) + if (dirty && DataSettings.DatabaseIsInstalled()) { // Save current hash of all deployed plugins to disk var hash = ComputePluginsHash(_referencedPlugins.Values.OrderBy(x => x.FolderName).ToArray()); @@ -259,6 +260,8 @@ private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollectio throw new SmartException("The plugin descriptor '{0}' does not define a plugin assembly file name. Try assigning the plugin a file name and recompile.".FormatInvariant(descriptionFile.FullName)); } + descriptor.VirtualPath = _pluginsPath + "/" + descriptor.FolderName; + // Set 'Installed' property descriptor.Installed = installedPluginSystemNames.Contains(descriptor.SystemName); diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/IProvider.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/IProvider.cs index f0dc56eb1a..fae747723b 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/IProvider.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/IProvider.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Web.Routing; namespace SmartStore.Core.Plugins { diff --git a/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs b/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs index da73ff4841..0fe4f85550 100644 --- a/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs +++ b/src/Libraries/SmartStore.Core/Search/DefaultIndexManager.cs @@ -4,7 +4,7 @@ namespace SmartStore.Core.Search { - public class DefaultIndexManager : IIndexManager + public class DefaultIndexManager : IIndexManager { private readonly IEnumerable> _providers; @@ -13,14 +13,14 @@ public DefaultIndexManager(IEnumerable> providers) _providers = providers; } - public bool HasAnyProvider(bool activeOnly = true) + public bool HasAnyProvider(string scope, bool activeOnly = true) { - return _providers.Any(x => !activeOnly || x.Value.IsActive); + return _providers.Any(x => !activeOnly || x.Value.IsActive(scope)); } - public IIndexProvider GetIndexProvider(bool activeOnly = true) + public IIndexProvider GetIndexProvider(string scope, bool activeOnly = true) { - return _providers.FirstOrDefault(x => !activeOnly || x.Value.IsActive)?.Value; + return _providers.FirstOrDefault(x => !activeOnly || x.Value.IsActive(scope))?.Value; } } } diff --git a/src/Libraries/SmartStore.Core/Search/Events/IndexSegmentProcessedEvent.cs b/src/Libraries/SmartStore.Core/Search/Events/IndexSegmentProcessedEvent.cs new file mode 100644 index 0000000000..b235921138 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Events/IndexSegmentProcessedEvent.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Search +{ + public class IndexSegmentProcessedEvent + { + public IndexSegmentProcessedEvent(string scope, IEnumerable operations, bool isRebuild) + { + Guard.NotEmpty(scope, nameof(scope)); + Guard.NotNull(operations, nameof(operations)); + + Scope = scope; + IsRebuild = isRebuild; + Operations = operations; + Metadata = new Dictionary(); + } + + public string Scope + { + get; + private set; + } + + public bool IsRebuild + { + get; + private set; + } + + public IEnumerable Operations + { + get; + private set; + } + + public IDictionary Metadata + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/Events/IndexingCompletedEvent.cs b/src/Libraries/SmartStore.Core/Search/Events/IndexingCompletedEvent.cs new file mode 100644 index 0000000000..b1304aae56 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Events/IndexingCompletedEvent.cs @@ -0,0 +1,27 @@ +using System; + +namespace SmartStore.Core.Search +{ + public class IndexingCompletedEvent + { + public IndexingCompletedEvent(IndexInfo indexInfo, bool wasRebuilt) + { + Guard.NotNull(indexInfo, nameof(indexInfo)); + + IndexInfo = indexInfo; + WasRebuilt = wasRebuilt; + } + + public IndexInfo IndexInfo + { + get; + private set; + } + + public bool WasRebuilt + { + get; + private set; + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs index bef397ea48..d0e8b5b8e7 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetDescriptor.cs @@ -29,34 +29,6 @@ public FacetDescriptor(string key) Key = key; } - /// - /// Gets the string resource key for a facet group kind - /// - /// Facet group kind - /// Resource key - public static string GetLabelResourceKey(FacetGroupKind kind) - { - switch (kind) - { - case FacetGroupKind.Category: - return "Search.Facet.Category"; - case FacetGroupKind.Brand: - return "Search.Facet.Manufacturer"; - case FacetGroupKind.Price: - return "Search.Facet.Price"; - case FacetGroupKind.Rating: - return "Search.Facet.Rating"; - case FacetGroupKind.DeliveryTime: - return "Search.Facet.DeliveryTime"; - case FacetGroupKind.Availability: - return "Search.Facet.Availability"; - case FacetGroupKind.NewArrivals: - return "Search.Facet.NewArrivals"; - default: - return null; - } - } - /// /// Gets the key / field name. /// diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs index cb0bd028ec..e6d9ffc48e 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetGroup.cs @@ -16,7 +16,10 @@ public enum FacetGroupKind Availability, NewArrivals, Attribute, - Variant + Variant, + Forum, + Customer, + Date } [DebuggerDisplay("Key: {Key}, Label: {Label}, Kind: {Kind}")] @@ -26,11 +29,12 @@ public class FacetGroup private FacetGroupKind? _kind; public FacetGroup() - : this (string.Empty, string.Empty, false, false, 0, Enumerable.Empty()) + : this (string.Empty, string.Empty, string.Empty, false, false, 0, Enumerable.Empty()) { } public FacetGroup( + string scope, string key, string label, bool isMultiSelect, @@ -38,13 +42,16 @@ public FacetGroup( int displayOrder, IEnumerable facets) { - Guard.NotNull(key, nameof(key)); + Guard.NotNull(scope, nameof(scope)); + Guard.NotNull(key, nameof(key)); Guard.NotNull(facets, nameof(facets)); + Scope = scope; Key = key; Label = label; IsMultiSelect = isMultiSelect; DisplayOrder = displayOrder; + IsScrollable = true; _facets = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -57,14 +64,14 @@ public FacetGroup( { _facets.Add(x.Key, x); } - catch (Exception exception) + catch (Exception ex) { - exception.Dump(); + ex.Dump(); } }); } - public static FacetGroupKind GetKindByKey(string key) + public static FacetGroupKind GetKindByKey(string scope, string key) { if (key.StartsWith("attrid")) { @@ -74,41 +81,40 @@ public static FacetGroupKind GetKindByKey(string key) { return FacetGroupKind.Variant; } - else if (key == "categoryid" || key == "notfeaturedcategoryid") - { - return FacetGroupKind.Category; - } - else if (key == "manufacturerid") - { - return FacetGroupKind.Brand; - } - else if (key == "price") - { - return FacetGroupKind.Price; - } - else if (key == "rating") - { - return FacetGroupKind.Rating; - } - else if (key == "deliveryid") - { - return FacetGroupKind.DeliveryTime; - } - else if (key == "available") - { - return FacetGroupKind.Availability; - } - else if (key == "createdon") - { - return FacetGroupKind.NewArrivals; - } - else - { - return FacetGroupKind.Unknown; - } - } - public string Key + switch (key) + { + case "categoryid": + case "notfeaturedcategoryid": + return FacetGroupKind.Category; + case "manufacturerid": + return FacetGroupKind.Brand; + case "price": + return FacetGroupKind.Price; + case "rating": + return FacetGroupKind.Rating; + case "deliveryid": + return FacetGroupKind.DeliveryTime; + case "available": + return FacetGroupKind.Availability; + case "createdon": + return scope == "Catalog" ? FacetGroupKind.NewArrivals : FacetGroupKind.Date; + case "forumid": + return FacetGroupKind.Forum; + case "customerid": + return FacetGroupKind.Customer; + default: + return FacetGroupKind.Unknown; + } + } + + public string Scope + { + get; + private set; + } + + public string Key { get; private set; @@ -126,7 +132,7 @@ public bool IsMultiSelect private set; } - public bool HasChildren + public bool HasChildren { get; private set; @@ -138,7 +144,13 @@ public int DisplayOrder private set; } - public IEnumerable Facets + public bool IsScrollable + { + get; + set; + } + + public IEnumerable Facets { get { @@ -170,7 +182,7 @@ public FacetGroupKind Kind { if (_kind == null) { - _kind = GetKindByKey(Key); + _kind = GetKindByKey(Scope, Key); } return _kind.Value; diff --git a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs index 0cb8d6d3e1..e3e451cab7 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs @@ -155,20 +155,38 @@ public override bool Equals(object obj) return this.Equals(obj as FacetValue); } + protected virtual string ConvertToString(object value) + { + if (value != null) + { + if (TypeCode == IndexTypeCode.DateTime) + { + // The default conversion is not pretty enough. + var dt = (DateTime)value; + if (dt.Hour == 0 && dt.Minute == 0 && dt.Second == 0) + { + return dt.ToString("yyyy-MM-dd"); + } + else + { + return dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + } + + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + return string.Empty; + } + public override string ToString() { var result = string.Empty; - - var valueStr = Value != null - ? Convert.ToString(Value, CultureInfo.InvariantCulture) - : string.Empty; + var valueStr = ConvertToString(Value); if (IsRange) { - var upperValueStr = UpperValue != null - ? Convert.ToString(UpperValue, CultureInfo.InvariantCulture) - : string.Empty; - + var upperValueStr = ConvertToString(UpperValue); if (upperValueStr.HasValue()) { result = string.Concat(valueStr, "~", upperValueStr); diff --git a/src/Libraries/SmartStore.Core/Search/IIndexManager.cs b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs index da56fc1280..72868d85b5 100644 --- a/src/Libraries/SmartStore.Core/Search/IIndexManager.cs +++ b/src/Libraries/SmartStore.Core/Search/IIndexManager.cs @@ -1,25 +1,25 @@ -using System; - -namespace SmartStore.Core.Search +namespace SmartStore.Core.Search { - /// - /// A simple factory for registered search index providers - /// - public interface IIndexManager + /// + /// A simple factory for registered search index providers + /// + public interface IIndexManager { - /// - /// Whether at least one provider is available, which implements - /// - /// Whether only active providers should be queried for - /// true if at least one provider is registered, false ortherwise - /// Primarily used to skip indexing processes - bool HasAnyProvider(bool activeOnly = true); + /// + /// Whether at least one provider is available, which implements + /// + /// Index scope name + /// Whether only active providers should be queried for + /// true if at least one provider is registered, false ortherwise + /// Primarily used to skip indexing processes + bool HasAnyProvider(string scope, bool activeOnly = true); /// /// Returns the instance of the first registered index provider (e.g. a Lucene provider) /// + /// Index scope name /// Whether only active providers should be queried for /// The index provider implementation instance - IIndexProvider GetIndexProvider(bool activeOnly = true); + IIndexProvider GetIndexProvider(string scope, bool activeOnly = true); } } diff --git a/src/Libraries/SmartStore.Core/Search/IIndexOperation.cs b/src/Libraries/SmartStore.Core/Search/IIndexOperation.cs new file mode 100644 index 0000000000..27453c616e --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IIndexOperation.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Core.Search +{ + public enum IndexOperationType + { + Index, + Delete + } + + /// + /// Represents an indexing operation + /// + public interface IIndexOperation + { + /// + /// The type of the operation + /// + IndexOperationType OperationType { get; } + + /// + /// The document being inserted to or deleted from the index storage + /// + IIndexDocument Document { get; } + + /// + /// The database entity from which was created + /// + BaseEntity Entity { get; } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs b/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs index e6c0825057..b63ac0261c 100644 --- a/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs +++ b/src/Libraries/SmartStore.Core/Search/IIndexProvider.cs @@ -4,16 +4,16 @@ namespace SmartStore.Core.Search { public interface IIndexProvider { - /// - /// Gets a value indicating whether the index provider is active - /// - bool IsActive { get; } + /// + /// Gets a value indicating whether the search index given by scope is active + /// + bool IsActive(string scope); - /// - /// Enumerates the names of all EXISTING indexes. - /// A name is required for the method. - /// - IEnumerable EnumerateIndexes(); + /// + /// Enumerates the names of all EXISTING indexes. + /// A name is required for the method. + /// + IEnumerable EnumerateIndexes(); /// /// Creates an empty document diff --git a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs index 70e9dcb508..2a17f673b0 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs @@ -47,13 +47,25 @@ public interface ISearchEngine /// Suggestions/corrections or an empty array string[] CheckSpelling(); - /// - /// Highlights chosen terms in a text, extracting the most relevant sections - /// - /// Text to highlight terms in - /// Text/HTML to prepend to matched keyword - /// Text/HTML to append to matched keyword - /// Highlighted text fragments - string Highlight(string input, string preMatch, string postMatch); - } + /// + /// Highlights chosen terms in a text, extracting the most relevant sections + /// + /// Text to highlight terms in + /// Field name + /// Text/HTML to prepend to matched keyword + /// Text/HTML to append to matched keyword + /// Highlighted text fragments + string Highlight(string input, string fieldName, string preMatch, string postMatch); + + /// + /// Gets highlighted text fragments for an entity identifier. + /// + /// Entity identifier. + /// Field name. + /// Text/HTML to prepend to matched keyword. + /// Text/HTML to append to matched keyword. + /// Maximum number of returned text fragments. + /// Highlighted text fragments. + string Highlight(int id, string fieldName, string preMatch, string postMatch, int maxNumFragments); + } } diff --git a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs index cf1bf1cca3..28ba070494 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchQuery.cs @@ -3,16 +3,16 @@ namespace SmartStore.Core.Search { - public interface ISearchQuery + public interface ISearchQuery { // Language, Currency & Store int? LanguageId { get; } string LanguageCulture { get; } string CurrencyCode { get; } - int? StoreId { get; } + int? StoreId { get; } - // Search term - string[] Fields { get; } + // Search term + string[] Fields { get; } string Term { get; } SearchMode Mode { get; } bool EscapeTerm { get; } diff --git a/src/Libraries/SmartStore.Core/Search/IndexInfo.cs b/src/Libraries/SmartStore.Core/Search/IndexInfo.cs new file mode 100644 index 0000000000..468754317f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IndexInfo.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace SmartStore.Core.Search +{ + public enum IndexingStatus + { + Unavailable = -1, + Idle = 0, + Rebuilding = 1, + Updating = 2 + } + + public class IndexInfo + { + public IndexInfo(string scope) + { + Guard.NotEmpty(scope, nameof(scope)); + + Scope = scope; + Fields = new string[0]; + } + + public string Scope { get; private set; } + public int DocumentCount { get; set; } + public IEnumerable Fields { get; set; } + + // loaded from status file + public DateTime? LastIndexedUtc { get; set; } + public TimeSpan? LastIndexingDuration { get; set; } + public bool IsModified { get; set; } + public IndexingStatus Status { get; set; } + public string Error { get; set; } + + /// + /// Indicates that the index should be rebuilt from scratch, because + /// some global settings have changed (like tax rates for example) + /// + public bool ShouldRebuild { get; set; } + + public string ToXml() + { + return new XDocument( + new XElement("info", + new XElement("status", Status), + new XElement("last-indexed-utc", LastIndexedUtc?.ToString("u")), + new XElement("last-indexing-duration", LastIndexingDuration?.ToString("c")), + new XElement("is-modified", IsModified ? "true" : "false"), + new XElement("error", Error), + new XElement("should-rebuild", ShouldRebuild ? "true" : "false"), + new XElement("document-count", DocumentCount), + new XElement("fields", string.Join(", ", Fields ?? Enumerable.Empty())) + )).ToString(); + } + + public static IndexInfo FromXml(string xml, string scope) + { + var info = new IndexInfo(scope); + + try + { + var doc = XDocument.Parse(xml); + + var lastIndexed = doc.Descendants("last-indexed-utc").FirstOrDefault()?.Value; + if (lastIndexed.HasValue()) + { + info.LastIndexedUtc = lastIndexed.Convert()?.ToUniversalTime(); + } + + var lastDuration = doc.Descendants("last-indexing-duration").FirstOrDefault()?.Value; + if (lastDuration.HasValue()) + { + info.LastIndexingDuration = lastDuration.Convert(); + } + + var isModified = doc.Descendants("is-modified").FirstOrDefault()?.Value; + if (isModified.HasValue()) + { + info.IsModified = isModified.Convert(); + } + else + { + info.IsModified = lastIndexed.HasValue(); + } + + var status = doc.Descendants("status").FirstOrDefault()?.Value; + if (status.HasValue()) + { + info.Status = status.Convert(); + } + + info.Error = doc.Descendants("error").FirstOrDefault()?.Value; + + var documentCount = doc.Descendants("document-count").FirstOrDefault()?.Value; + if (documentCount.HasValue()) + { + info.DocumentCount = documentCount.ToInt(); + } + + var fields = doc.Descendants("fields").FirstOrDefault()?.Value; + if (fields.HasValue()) + { + info.Fields = fields.SplitSafe(", "); + } + + var shouldRebuild = doc.Descendants("should-rebuild").FirstOrDefault()?.Value; + if (shouldRebuild.HasValue()) + { + info.ShouldRebuild = shouldRebuild.Convert(); + } + } + catch { } + + return info; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs b/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs index 4b6caa0796..15662c3768 100644 --- a/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs +++ b/src/Libraries/SmartStore.Core/Search/IndexProviderBase.cs @@ -4,15 +4,12 @@ namespace SmartStore.Core.Search { public abstract class IndexProviderBase : IIndexProvider { - public virtual bool IsActive - { - get - { - return true; - } - } + public virtual bool IsActive(string scope) + { + return true; + } - public abstract IEnumerable EnumerateIndexes(); + public abstract IEnumerable EnumerateIndexes(); public virtual IIndexDocument CreateDocument(int id, SearchDocumentType? documentType) { diff --git a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs index fe80ff0036..c2597f571d 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchDocumentType.cs @@ -9,6 +9,9 @@ public enum SearchDocumentType Attribute, AttributeValue, Variant, - VariantValue + VariantValue, + Customer, + Forum, + ForumPost } } diff --git a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs index 6cd4f78b9e..18cfcd3f83 100644 --- a/src/Libraries/SmartStore.Core/Search/SearchQuery.cs +++ b/src/Libraries/SmartStore.Core/Search/SearchQuery.cs @@ -7,7 +7,7 @@ namespace SmartStore.Core.Search { - public enum SearchResultFlags + public enum SearchResultFlags { WithHits = 1 << 0, WithFacets = 1 << 1, @@ -65,10 +65,10 @@ protected SearchQuery(string[] fields, string term, SearchMode mode = SearchMode public int? LanguageId { get; protected set; } public string LanguageCulture { get; protected set; } public string CurrencyCode { get; protected set; } - public int? StoreId { get; protected set; } + public int? StoreId { get; protected set; } - // Search term - public string[] Fields { get; set; } + // Search term + public string[] Fields { get; set; } public string Term { get; set; } public bool EscapeTerm { get; protected set; } public SearchMode Mode { get; protected set; } @@ -164,7 +164,7 @@ public TQuery Slice(int skip, int take) public TQuery CheckSpelling(int maxSuggestions, int minQueryLength = 4, int maxHitCount = 3) { - Guard.IsPositive(maxSuggestions, nameof(maxSuggestions)); + Guard.NotNegative(maxSuggestions, nameof(maxSuggestions)); Guard.IsPositive(minQueryLength, nameof(minQueryLength)); Guard.IsPositive(maxHitCount, nameof(maxHitCount)); diff --git a/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs new file mode 100644 index 0000000000..b213e2e56b --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/Settings/ForumSearchSettings.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Core.Search +{ + public class ForumSearchSettings : ISettings + { + public ForumSearchSettings() + { + SearchMode = SearchMode.Contains; + SearchFields = new List { "username", "text" }; + DefaultSortOrder = ForumTopicSorting.Relevance; + InstantSearchEnabled = true; + InstantSearchNumberOfHits = 10; + InstantSearchTermMinLength = 2; + FilterMinHitCount = 1; + FilterMaxChoicesCount = 20; + + ForumDisplayOrder = 1; + CustomerDisplayOrder = 2; + DateDisplayOrder = 3; + } + + /// + /// Gets or sets the search mode. + /// + public SearchMode SearchMode { get; set; } + + /// + /// Gets or sets name of fields to be searched. The name field is always searched. + /// + public List SearchFields { get; set; } + + /// + /// Gets or sets the default sort order in search results. + /// + public ForumTopicSorting DefaultSortOrder { get; set; } + + /// + /// Gets or sets a value indicating whether instant-search is enabled. + /// + public bool InstantSearchEnabled { get; set; } + + /// + /// Gets or sets the number of hits to return when using "instant-search" feature. + /// + public int InstantSearchNumberOfHits { get; set; } + + /// + /// Gets or sets a minimum instant-search term length. + /// + public int InstantSearchTermMinLength { get; set; } + + /// + /// Gets or sets the minimum hit count for a filter value. Values with a lower hit count are not displayed. + /// + public int FilterMinHitCount { get; set; } + + /// + /// Gets or sets the maximum number of filter values to be displayed. + /// + public int FilterMaxChoicesCount { get; set; } + + #region Common facet settings + + public bool ForumDisabled { get; set; } + public bool CustomerDisabled { get; set; } + public bool DateDisabled { get; set; } + + public int ForumDisplayOrder { get; set; } + public int CustomerDisplayOrder { get; set; } + public int DateDisplayOrder { get; set; } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Search/SearchSettings.cs b/src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs similarity index 100% rename from src/Libraries/SmartStore.Core/Search/SearchSettings.cs rename to src/Libraries/SmartStore.Core/Search/Settings/SearchSettings.cs diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 023bb38722..98f3bb0cb0 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -182,6 +182,7 @@ + @@ -209,6 +210,7 @@ + @@ -216,6 +218,9 @@ + + + @@ -228,19 +233,23 @@ + + + + - + @@ -254,6 +263,9 @@ + + + @@ -266,6 +278,8 @@ + + @@ -273,6 +287,7 @@ + @@ -429,6 +444,7 @@ + @@ -440,11 +456,14 @@ + + + @@ -456,7 +475,8 @@ - + + @@ -505,7 +525,6 @@ - @@ -516,7 +535,6 @@ - diff --git a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs index cb55e7d3ed..75f292f08f 100644 --- a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs +++ b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs @@ -6,7 +6,27 @@ namespace SmartStore.Core { - public static class SmartStoreVersion + public class HelpTopic + { + public static HelpTopic CronExpressions = new HelpTopic("cron", "Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron", "Geplante+Aufgaben+verwalten#GeplanteAufgabenverwalten-CronAusdruck"); + + public HelpTopic(string name, string enPath, string dePath) + { + Guard.NotEmpty(name, nameof(name)); + Guard.NotEmpty(enPath, nameof(enPath)); + Guard.NotEmpty(dePath, nameof(dePath)); + + Name = name; + EnPath = enPath; + DePath = dePath; + } + + public string Name { get; private set; } + public string EnPath { get; private set; } + public string DePath { get; private set; } + } + + public static class SmartStoreVersion { private static readonly Version s_infoVersion = new Version("1.0.0.0"); private static readonly List s_breakingChangesHistory = new List @@ -16,15 +36,17 @@ public static class SmartStoreVersion // A release with breaking changes should definitely have at least // a greater minor version. new Version("1.2"), - new Version("1.2.1"), // MC: had to be :-( + new Version("1.2.1"), new Version("2.0"), new Version("2.1"), new Version("2.2"), new Version("2.5"), - new Version("3.0") + new Version("3.0"), + new Version("3.1"), + new Version("3.1.5") }; - private const string HELP_BASEURL = "http://docs.smartstore.com/display/"; + private const string HELP_BASEURL = "https://docs.smartstore.com/display/"; static SmartStoreVersion() { @@ -68,8 +90,19 @@ public static Version Version } } + public static string GenerateHelpUrl(string languageCode, HelpTopic topic) + { + Guard.NotEmpty(languageCode, nameof(languageCode)); + Guard.NotNull(topic, nameof(topic)); + + var path = languageCode.IsCaseInsensitiveEqual("de") ? topic.DePath : topic.EnPath; + return GenerateHelpUrl(languageCode, path); + } + public static string GenerateHelpUrl(string languageCode, string path) { + Guard.NotEmpty(languageCode, nameof(languageCode)); + return String.Concat( HELP_BASEURL, GetUserGuideSpaceKey(languageCode), @@ -79,10 +112,9 @@ public static string GenerateHelpUrl(string languageCode, string path) public static string GetUserGuideSpaceKey(string languageCode) { - if(languageCode.Equals("de")) - return "SDDE30"; - - return "SMNET30"; + return languageCode.IsCaseInsensitiveEqual("de") + ? "SDDE31" + : "SMNET31"; } /// diff --git a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs index 6e3c043b89..5a4a2253b2 100644 --- a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs +++ b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs @@ -435,7 +435,8 @@ private void OnThemeFileChanged(string name, string fullPath, ThemeFileChangeTyp RaiseBaseThemeChanged(baseThemeChangedArgs); } - RaiseThemeFileChanged(new ThemeFileChangedEventArgs { + RaiseThemeFileChanged(new ThemeFileChangedEventArgs + { ChangeType = changeType, FullPath = fullPath, ThemeName = themeName, diff --git a/src/Libraries/SmartStore.Core/Themes/ThemeFolderData.cs b/src/Libraries/SmartStore.Core/Themes/ThemeFolderData.cs index 3bac8d4084..e557505cc9 100644 --- a/src/Libraries/SmartStore.Core/Themes/ThemeFolderData.cs +++ b/src/Libraries/SmartStore.Core/Themes/ThemeFolderData.cs @@ -7,6 +7,7 @@ internal class ThemeFolderData : ITopologicSortable { public string FolderName { get; set; } public string FullPath { get; set; } + public bool IsSymbolicLink { get; set; } public string VirtualBasePath { get; set; } public XmlDocument Configuration { get; set; } public string BaseTheme { get; set; } diff --git a/src/Libraries/SmartStore.Core/Themes/ThemeManifest.cs b/src/Libraries/SmartStore.Core/Themes/ThemeManifest.cs index 739c8a40b3..dbaca7e630 100644 --- a/src/Libraries/SmartStore.Core/Themes/ThemeManifest.cs +++ b/src/Libraries/SmartStore.Core/Themes/ThemeManifest.cs @@ -1,11 +1,8 @@ using System; -using System.Linq; using System.Collections.Generic; using System.Xml; using SmartStore.Collections; using System.IO; -using SmartStore.Utilities; -using System.Web; namespace SmartStore.Core.Themes { @@ -46,7 +43,15 @@ internal static ThemeFolderData CreateThemeFolderData(string themePath, string v return null; virtualBasePath = virtualBasePath.EnsureEndsWith("/"); + var themeDirectory = new DirectoryInfo(themePath); + + var isSymLink = themeDirectory.IsSymbolicLink(); + if (isSymLink) + { + themeDirectory = new DirectoryInfo(themeDirectory.GetFinalPathName()); + } + var themeConfigFile = new FileInfo(System.IO.Path.Combine(themeDirectory.FullName, "theme.config")); if (themeConfigFile.Exists) @@ -69,6 +74,7 @@ internal static ThemeFolderData CreateThemeFolderData(string themePath, string v { FolderName = themeDirectory.Name, FullPath = themeDirectory.FullName, + IsSymbolicLink = isSymLink, Configuration = doc, VirtualBasePath = virtualBasePath, BaseTheme = baseTheme @@ -97,28 +103,32 @@ public string Location protected internal set; } - /// - /// Gets the physical path of the theme - /// - public string Path - { - get; - protected internal set; + /// + /// Determines whether the theme directory is a symbolic link to another target. + /// + public bool IsSymbolicLink + { + get; + protected internal set; } - public string PreviewImageUrl - { - get; - protected internal set; + /// + /// Gets the physical path of the theme. In case of a symbolic link, + /// returns the link's target path. + /// + public string Path + { + get; + protected internal set; } - public string PreviewText + public string PreviewImageUrl { get; protected internal set; } - public bool SupportRtl + public string PreviewText { get; protected internal set; @@ -178,14 +188,16 @@ public IDictionary Variables } var baseVars = this.BaseTheme.Variables; - var merged = new Dictionary(baseVars, StringComparer.OrdinalIgnoreCase); + var mergedVars = new Dictionary(baseVars, StringComparer.OrdinalIgnoreCase); + var newVars = new List(); + foreach (var localVar in _variables) { - if (merged.ContainsKey(localVar.Key)) + if (mergedVars.ContainsKey(localVar.Key)) { // Overridden var in child: update existing. - var baseVar = merged[localVar.Key]; - merged[localVar.Key] = new ThemeVariableInfo + var baseVar = mergedVars[localVar.Key]; + mergedVars[localVar.Key] = new ThemeVariableInfo { Name = baseVar.Name, Type = baseVar.Type, @@ -196,11 +208,25 @@ public IDictionary Variables } else { - // New var in child: add to list. - merged.Add(localVar.Key, localVar.Value); + // New var in child: add to temp list. + newVars.Add(localVar.Value); } } + var merged = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var newVar in newVars) + { + // New child theme vars must come first in final list + // to avoid wrong references in existing vars + merged.Add(newVar.Name, newVar); + } + + foreach (var kvp in mergedVars) + { + merged.Add(kvp.Key, kvp.Value); + } + return merged; } internal set diff --git a/src/Libraries/SmartStore.Core/Themes/ThemeManifestMaterializer.cs b/src/Libraries/SmartStore.Core/Themes/ThemeManifestMaterializer.cs index 6fadd1910e..98f1058002 100644 --- a/src/Libraries/SmartStore.Core/Themes/ThemeManifestMaterializer.cs +++ b/src/Libraries/SmartStore.Core/Themes/ThemeManifestMaterializer.cs @@ -22,6 +22,7 @@ public ThemeManifestMaterializer(ThemeFolderData folderData) _manifest.BaseThemeName = folderData.BaseTheme; _manifest.Location = folderData.VirtualBasePath; _manifest.Path = folderData.FullPath; + _manifest.IsSymbolicLink = folderData.IsSymbolicLink; _manifest.ConfigurationNode = folderData.Configuration.DocumentElement; } @@ -30,7 +31,6 @@ public ThemeManifest Materialize() var root = _manifest.ConfigurationNode; _manifest.ThemeTitle = root.GetAttribute("title").NullEmpty() ?? _manifest.ThemeName; - _manifest.SupportRtl = root.GetAttribute("supportRTL").ToBool(); _manifest.PreviewImageUrl = root.GetAttribute("previewImageUrl").NullEmpty() ?? "~/Themes/{0}/preview.png".FormatCurrent(_manifest.ThemeName); _manifest.PreviewText = root.GetAttribute("previewText").ToSafe(); _manifest.Author = root.GetAttribute("author").ToSafe(); diff --git a/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs b/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs index a6d0649e24..1c8aaad909 100644 --- a/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs @@ -10,6 +10,9 @@ using System.Web.Hosting; using System.Web.Mvc; using SmartStore.ComponentModel; +using System.Text; +using Newtonsoft.Json; +using System.Runtime.Serialization.Formatters.Binary; namespace SmartStore.Utilities { @@ -265,5 +268,99 @@ public static bool IsTruthy(object value) return true; } + + public static long GetObjectSizeInBytes(object obj, HashSet instanceLookup = null) + { + if (obj == null) + return 0; + + var type = obj.GetType(); + var genericArguments = type.GetGenericArguments(); + + long size = 0; + + if (obj is string str) + { + size = Encoding.Default.GetByteCount(str); + } + else if (obj is StringBuilder sb) + { + size = Encoding.Default.GetByteCount(sb.ToString()); + } + else if (type.IsEnum) + { + size = System.Runtime.InteropServices.Marshal.SizeOf(Enum.GetUnderlyingType(type)); + } + else if (type.IsPredefinedSimpleType() || type.IsPredefinedGenericType()) + { + //size = System.Runtime.InteropServices.Marshal.SizeOf(Nullable.GetUnderlyingType(type) ?? type); // crashes often + size = 8; // mean/average + } + else if (obj is Stream stream) + { + size = stream.Length; + } + else if (obj is IDictionary dic) + { + foreach (var item in dic.Values) + { + size += GetObjectSizeInBytes(item, instanceLookup); + } + } + else if (obj is IEnumerable e) + { + foreach (var item in e) + { + size += GetObjectSizeInBytes(item, instanceLookup); + } + } + else + { + if (instanceLookup == null) + { + instanceLookup = new HashSet(ReferenceEqualityComparer.Default); + } + + if (!type.IsValueType && instanceLookup.Contains(obj)) + { + return 0; + } + + instanceLookup.Add(obj); + + var serialized = false; + + if (type.IsSerializable && genericArguments.All(x => x.IsSerializable)) + { + try + { + using (var s = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(s, obj); + size = s.Length; + + serialized = true; + } + } + catch { } + } + + if (!serialized) + { + // Serialization failed or is not supported: make JSON. + var json = JsonConvert.SerializeObject(obj, new JsonSerializerSettings + { + DateFormatHandling = DateFormatHandling.IsoDateFormat, + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + MaxDepth = 10, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + }); + size = Encoding.Default.GetByteCount(json); + } + } + + return size; + } } } diff --git a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs index f9a5953de9..c4c458a569 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using System.Web; using SmartStore.Core; +using SmartStore.Core.IO; using SmartStore.Core.Logging; namespace SmartStore.Utilities @@ -22,7 +23,7 @@ public class FileDownloadManager public FileDownloadManager(HttpRequestBase httpRequest) { - this._httpRequest = httpRequest; + _httpRequest = httpRequest; } /// @@ -149,29 +150,45 @@ private async Task ProcessUrl(FileDownloadManagerContext context, HttpClient cli { try { - //HttpResponseMessage response = await client.GetAsync(item.Url, HttpCompletionOption.ResponseHeadersRead); - //Task task = response.Content.ReadAsStreamAsync(); - - Task task = client.GetStreamAsync(item.Url); - await task; - var count = 0; var canceled = false; var bytes = new byte[_bufferSize]; - using (var srcStream = task.Result) - using (var dstStream = File.Open(item.Path, FileMode.Create)) + using (var response = await client.GetAsync(item.Url)) { - while ((count = srcStream.Read(bytes, 0, bytes.Length)) != 0 && !canceled) + if (response.IsSuccessStatusCode && response.Content.Headers.ContentType != null) { - dstStream.Write(bytes, 0, count); + var contentType = response.Content.Headers.ContentType.MediaType; + if (contentType.HasValue() && !contentType.IsCaseInsensitiveEqual(item.MimeType)) + { + // Update mime type and local path. + var extension = MimeTypes.MapMimeTypeToExtension(contentType).NullEmpty() ?? ".jpg"; - if (context.CancellationToken != null && context.CancellationToken.IsCancellationRequested) - canceled = true; + item.MimeType = contentType; + item.Path = Path.ChangeExtension(item.Path, extension.EnsureStartsWith(".")); + } } - } - item.Success = (!task.IsFaulted && !canceled); + //Task task = client.GetStreamAsync(item.Url); + Task task = response.Content.ReadAsStreamAsync(); + await task; + + using (var srcStream = task.Result) + using (var dstStream = File.Open(item.Path, FileMode.Create)) + { + while ((count = srcStream.Read(bytes, 0, bytes.Length)) != 0 && !canceled) + { + dstStream.Write(bytes, 0, count); + + if (context.CancellationToken != null && context.CancellationToken.IsCancellationRequested) + { + canceled = true; + } + } + } + + item.Success = (!task.IsFaulted && !canceled); + } } catch (Exception exception) { @@ -199,9 +216,9 @@ public FileDownloadResponse(byte[] data, string fileName, string contentType) { Guard.NotNull(data, nameof(data)); - this.Data = data; - this.FileName = fileName; - this.ContentType = contentType; + Data = data; + FileName = fileName; + ContentType = contentType; } /// @@ -308,7 +325,7 @@ public bool HasTimedOut public override string ToString() { - string str = "Result: {0} {1}{2}, {3}".FormatInvariant( + var str = "Result: {0} {1}{2}, {3}".FormatInvariant( Success, ExceptionStatus.ToString(), ErrorMessage.HasValue() ? " ({0})".FormatInvariant(ErrorMessage) : "", diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index 7149375996..4eaf2bd093 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -7,7 +7,7 @@ namespace SmartStore.Utilities { - public static class FileSystemHelper + public static class FileSystemHelper { /// /// Returns physical path to application temp directory @@ -71,10 +71,32 @@ public static string ValidateRootPath(string path) return path; } - /// - /// Safe way to cleanup the temp directory. Should be called via scheduled task. - /// - public static void TempCleanup() + /// + /// Checks whether a path is a safe root path. + /// + /// Relative path + public static bool IsSafeRootPath(string path) + { + if (path.EmptyNull().Length > 2 && + !path.IsCaseInsensitiveEqual("con") && + path.IndexOfAny(Path.GetInvalidPathChars()) == -1) + { + try + { + var mappedPath = CommonHelper.MapPath(path); + var appPath = CommonHelper.MapPath("~/"); + return !mappedPath.IsCaseInsensitiveEqual(appPath); + } + catch { } + } + + return false; + } + + /// + /// Safe way to cleanup the temp directory. Should be called via scheduled task. + /// + public static void TempCleanup() { try { diff --git a/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs b/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs index d0c2db8dec..d43560f2e3 100644 --- a/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs +++ b/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs @@ -1,10 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.IO; -using System.Linq; using System.Runtime.CompilerServices; -using System.Threading.Tasks; /* Copied over from Microsoft.Framework.Internal @@ -22,6 +21,12 @@ public int CombinedHash get { return _combinedHash64.GetHashCode(); } } + public string CombinedHashString + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode().ToString("x", CultureInfo.InvariantCulture); } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private HashCodeCombiner(long seed) { diff --git a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs index 6613ba70f6..20c4b22140 100644 --- a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs @@ -17,11 +17,11 @@ public static class SeoHelper /// /// String to be converted /// A value indicating whether non western chars should be converted - /// A value indicating whether Unicode chars are allowed + /// A value indicating whether Unicode chars are allowed /// Raw data of semicolon separated char conversions /// SEO friendly string [SuppressMessage("ReSharper", "PossibleNullReferenceException")] - public static string GetSeName(string name, bool convertNonWesternChars, bool allowUnicodeCharsInUrls, string charConversions = null) + public static string GetSeName(string name, bool convertNonWesternChars, bool allowUnicodeChars, string charConversions = null) { if (String.IsNullOrEmpty(name)) return name; @@ -54,7 +54,7 @@ public static string GetSeName(string name, bool convertNonWesternChars, bool al c2 = _seoCharacterTable[c2]; } - if (_okChars.Contains(c2) || (allowUnicodeCharsInUrls && char.IsLetterOrDigit(c))) + if (_okChars.Contains(c2) || (allowUnicodeChars && char.IsLetterOrDigit(c))) { sb.Append(c2); } diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index 4d6c93c601..6304d682bd 100644 --- a/src/Libraries/SmartStore.Core/WebHelper.cs +++ b/src/Libraries/SmartStore.Core/WebHelper.cs @@ -25,7 +25,7 @@ public partial class WebHelper : IWebHelper private static object s_lock = new object(); private static bool? s_optimizedCompilationsEnabled; private static AspNetHostingPermissionLevel? s_trustLevel; - private static readonly Regex s_staticExts = new Regex(@"(.*?)\.(css|js|png|jpg|jpeg|gif|webp|scss|less|liquid|bmp|html|htm|xml|pdf|doc|xls|rar|zip|7z|ico|eot|svg|ttf|woff|otf|axd|ashx)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex s_staticExts = new Regex(@"(.*?)\.(css|js|png|jpg|jpeg|gif|webp|liquid|bmp|html|htm|xml|pdf|doc|xls|rar|zip|7z|ico|eot|svg|ttf|woff|woff2|otf)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex s_htmlPathPattern = new Regex(@"(?<=(?:href|src)=(?:""|'))(?!https?://)(?[^(?:""|')]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); private static readonly Regex s_cssPathPattern = new Regex(@"url\('(?.+)'\)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Multiline); private static ConcurrentDictionary s_safeLocalHostNames = new ConcurrentDictionary(); @@ -42,19 +42,12 @@ public partial class WebHelper : IWebHelper public WebHelper(HttpContextBase httpContext) { - this._httpContext = httpContext; + _httpContext = httpContext; } public virtual string GetUrlReferrer() { - string referrerUrl = null; - - if (_httpContext != null && - _httpContext.Request != null && - _httpContext.Request.UrlReferrer != null) - referrerUrl = _httpContext.Request.UrlReferrer.ToString(); - - return referrerUrl.EmptyNull(); + return _httpContext?.Request?.UrlReferrer?.ToString() ?? string.Empty; } public virtual string GetClientIdent() @@ -133,7 +126,7 @@ public virtual string GetThisPageUrl(bool includeQueryString) public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl) { string url = string.Empty; - if (_httpContext == null || _httpContext.Request == null) + if (_httpContext?.Request == null) return url; if (includeQueryString) @@ -619,7 +612,7 @@ public static string MakeAllUrlsAbsolute(string html, string protocol, string ho Guard.NotEmpty(protocol, nameof(protocol)); Guard.NotEmpty(host, nameof(host)); - string baseUrl = string.Format("{0}://{1}", protocol, host.TrimEnd('/')); + string baseUrl = protocol.EnsureEndsWith("://") + host.TrimEnd('/'); MatchEvaluator evaluator = (match) => { @@ -637,7 +630,7 @@ public static string MakeAllUrlsAbsolute(string html, string protocol, string ho /// Prepends protocol and host to the given (relative) url /// [SuppressMessage("ReSharper", "AccessToModifiedClosure")] - public static string GetAbsoluteUrl(string url, HttpRequestBase request) + public static string GetAbsoluteUrl(string url, HttpRequestBase request, bool enforceScheme = false) { Guard.NotEmpty(url, nameof(url)); Guard.NotNull(request, nameof(request)); @@ -647,11 +640,18 @@ public static string GetAbsoluteUrl(string url, HttpRequestBase request) return url; } - if (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + if (url.Contains("://")) { return url; } + if (url.StartsWith("//")) + { + return enforceScheme + ? String.Concat(request.Url.Scheme, ":", url) + : url; + } + if (url.StartsWith("~")) { url = VirtualPathUtility.ToAbsolute(url); diff --git a/src/Libraries/SmartStore.Data/Caching/CachingCommand.cs b/src/Libraries/SmartStore.Data/Caching/CachingCommand.cs index 82fe3e4492..93dda75187 100644 --- a/src/Libraries/SmartStore.Data/Caching/CachingCommand.cs +++ b/src/Libraries/SmartStore.Data/Caching/CachingCommand.cs @@ -353,9 +353,7 @@ public async override Task ExecuteScalarAsync(CancellationToken cancella var key = CreateKey(); - object value; - - if (_cacheTransactionInterceptor.GetItem(Transaction, key, out value)) + if (_cacheTransactionInterceptor.GetItem(Transaction, key, out var value)) { return value; } diff --git a/src/Libraries/SmartStore.Data/Caching/DbCachingPolicy.cs b/src/Libraries/SmartStore.Data/Caching/DbCachingPolicy.cs index 08feb99172..9dae29bd05 100644 --- a/src/Libraries/SmartStore.Data/Caching/DbCachingPolicy.cs +++ b/src/Libraries/SmartStore.Data/Caching/DbCachingPolicy.cs @@ -57,7 +57,6 @@ public partial class DbCachingPolicy typeof(ShippingMethod).Name, typeof(StateProvince).Name, typeof(Store).Name, - typeof(StoreMapping).Name, typeof(TaxCategory).Name, typeof(ThemeVariable).Name, typeof(Topic).Name diff --git a/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs b/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs index 4c26f648f9..e7e2b37c00 100644 --- a/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs +++ b/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Security.Cryptography; using System.Text; -using System.Threading.Tasks; using SmartStore.Core.Caching; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Messages; @@ -18,12 +17,13 @@ public partial class EfDbCache : IDbCache private static readonly HashSet _toxicSets = new HashSet { typeof(ScheduleTask).Name, + typeof(ScheduleTaskHistory).Name, typeof(Log).Name, typeof(ActivityLog).Name, typeof(QueuedEmail).Name }; - private const string KEYPREFIX = "efcache:*"; + private const string KEYPREFIX = "efcache:"; private readonly object _lock = new object(); private bool _enabled; @@ -248,8 +248,8 @@ public virtual DbCacheEntry Put(string key, object value, IEnumerable de public void Clear() { - _cache.RemoveByPattern(KEYPREFIX); - _requestCache.Value.RemoveByPattern(KEYPREFIX); + _cache.RemoveByPattern(KEYPREFIX + "*"); + _requestCache.Value.RemoveByPattern(KEYPREFIX + "*"); } public virtual void InvalidateSets(IEnumerable entitySets) @@ -361,8 +361,14 @@ private static string HashKey(string key) using (var sha = new SHA1CryptoServiceProvider()) { - key = Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(key))); - return KEYPREFIX + "data:" + key; + try + { + return KEYPREFIX + "data:" + Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(key))); + } + catch + { + return KEYPREFIX + "data:" + key; + } } } } diff --git a/src/Libraries/SmartStore.Data/Extensions/DbEntityEntryExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/DbEntityEntryExtensions.cs index 0a64a2aa15..9d5688cac9 100644 --- a/src/Libraries/SmartStore.Data/Extensions/DbEntityEntryExtensions.cs +++ b/src/Libraries/SmartStore.Data/Extensions/DbEntityEntryExtensions.cs @@ -2,13 +2,7 @@ using System.Collections.Generic; using System.Data.Entity.Infrastructure; using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using SmartStore.ComponentModel; -using SmartStore.Core; using SmartStore.Core.Data; -using SmartStore.Utilities; using EfState = System.Data.Entity.EntityState; namespace SmartStore.Data diff --git a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductAttributeMap.cs b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductAttributeMap.cs index d2cb42ee35..466c56ebd7 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductAttributeMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductAttributeMap.cs @@ -7,10 +7,11 @@ public partial class ProductAttributeMap : EntityTypeConfiguration pa.Id); - this.Property(pa => pa.Alias).HasMaxLength(100); - this.Property(pa => pa.Name).IsRequired(); - } + ToTable("ProductAttribute"); + HasKey(pa => pa.Id); + Property(pa => pa.Alias).HasMaxLength(100); + Property(pa => pa.Name).IsRequired(); + Property(pa => pa.ExportMappings).IsMaxLength(); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductCategoryMap.cs b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductCategoryMap.cs index 3009d11fb1..e152d7c5ca 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductCategoryMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductCategoryMap.cs @@ -14,7 +14,6 @@ public ProductCategoryMap() .WithMany() .HasForeignKey(pc => pc.CategoryId); - this.HasRequired(pc => pc.Product) .WithMany(p => p.ProductCategories) .HasForeignKey(pc => pc.ProductId); diff --git a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductMap.cs b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductMap.cs index 393c8bb832..042db6e7f2 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Catalog/ProductMap.cs @@ -32,6 +32,7 @@ public ProductMap() this.Property(p => p.RequiredProductIds).HasMaxLength(1000); this.Property(p => p.AllowedQuantities).HasMaxLength(1000); this.Property(p => p.CustomsTariffNumber).HasMaxLength(30); + this.Property(p => p.SystemName).HasMaxLength(400); this.HasOptional(p => p.DeliveryTime) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Mapping/Customers/CustomerMap.cs b/src/Libraries/SmartStore.Data/Mapping/Customers/CustomerMap.cs index 6004f3a520..06582eade9 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Customers/CustomerMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Customers/CustomerMap.cs @@ -17,7 +17,15 @@ public CustomerMap() this.Property(u => u.PasswordSalt).HasMaxLength(500); this.Property(u => u.LastIpAddress).HasMaxLength(100); - this.Ignore(u => u.PasswordFormat); + this.Property(u => u.Title).HasMaxLength(100); + this.Property(u => u.Salutation).HasMaxLength(50); + this.Property(u => u.FirstName).HasMaxLength(225); + this.Property(u => u.LastName).HasMaxLength(225); + this.Property(u => u.FullName).HasMaxLength(450); + this.Property(u => u.Company).HasMaxLength(255); + this.Property(u => u.CustomerNumber).HasMaxLength(100); + + this.Ignore(u => u.PasswordFormat); this.HasMany(c => c.CustomerRoles) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Mapping/Customers/WalletHistoryMap.cs b/src/Libraries/SmartStore.Data/Mapping/Customers/WalletHistoryMap.cs new file mode 100644 index 0000000000..1db98316dc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Mapping/Customers/WalletHistoryMap.cs @@ -0,0 +1,29 @@ +using System.Data.Entity.ModelConfiguration; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Data.Mapping.Customers +{ + public partial class WalletHistoryMap : EntityTypeConfiguration + { + public WalletHistoryMap() + { + ToTable("WalletHistory"); + HasKey(x => x.Id); + + Property(x => x.Amount).HasPrecision(18, 4); + Property(x => x.AmountBalance).HasPrecision(18, 4); + Property(x => x.AmountBalancePerStore).HasPrecision(18, 4); + Property(x => x.Message).HasMaxLength(1000); + Property(x => x.AdminComment).HasMaxLength(4000); + + HasRequired(x => x.Customer) + .WithMany(x => x.WalletHistory) + .HasForeignKey(x => x.CustomerId); + + HasOptional(x => x.Order) + .WithMany(x => x.WalletHistory) + .HasForeignKey(x => x.OrderId) + .WillCascadeOnDelete(false); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs index e6fc71352b..1a8b46113b 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumTopicMap.cs @@ -7,16 +7,18 @@ public partial class ForumTopicMap : EntityTypeConfiguration { public ForumTopicMap() { - this.ToTable("Forums_Topic"); - this.HasKey(ft => ft.Id); - this.Property(ft => ft.Subject).IsRequired().HasMaxLength(450); - this.Ignore(ft => ft.ForumTopicType); + ToTable("Forums_Topic"); + HasKey(ft => ft.Id); + Property(ft => ft.Subject).IsRequired().HasMaxLength(450); - this.HasRequired(ft => ft.Forum) + Ignore(ft => ft.ForumTopicType); + Ignore(ft => ft.FirstPostId); + + HasRequired(ft => ft.Forum) .WithMany() .HasForeignKey(ft => ft.ForumId); - this.HasRequired(ft => ft.Customer) + HasRequired(ft => ft.Customer) .WithMany(c => c.ForumTopics) .HasForeignKey(ft => ft.CustomerId) .WillCascadeOnDelete(false); diff --git a/src/Libraries/SmartStore.Data/Mapping/Orders/OrderMap.cs b/src/Libraries/SmartStore.Data/Mapping/Orders/OrderMap.cs index b06180e269..a0378d4303 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Orders/OrderMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Orders/OrderMap.cs @@ -22,10 +22,11 @@ public OrderMap() this.Property(o => o.PaymentMethodAdditionalFeeTaxRate).HasPrecision(18, 4); this.Property(o => o.OrderTax).HasPrecision(18, 4); this.Property(o => o.OrderDiscount).HasPrecision(18, 4); - this.Property(o => o.OrderTotalRounding).HasPrecision(18, 4); + this.Property(o => o.CreditBalance).HasPrecision(18, 4); + this.Property(o => o.OrderTotalRounding).HasPrecision(18, 4); this.Property(o => o.OrderTotal).HasPrecision(18, 4); this.Property(o => o.RefundedAmount).HasPrecision(18, 4); - this.Property(o => o.OrderNumber).IsOptional(); + this.Property(o => o.OrderNumber).IsOptional(); this.Ignore(o => o.OrderStatus); this.Ignore(o => o.PaymentStatus); diff --git a/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs new file mode 100644 index 0000000000..ff483c81ee --- /dev/null +++ b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskHistoryMap.cs @@ -0,0 +1,21 @@ +using System.Data.Entity.ModelConfiguration; +using SmartStore.Core.Domain.Tasks; + +namespace SmartStore.Data.Mapping.Tasks +{ + public partial class ScheduleTaskHistoryMap : EntityTypeConfiguration + { + public ScheduleTaskHistoryMap() + { + ToTable("ScheduleTaskHistory"); + HasKey(x => x.Id); + Property(x => x.MachineName).HasMaxLength(400); + Property(x => x.Error).HasMaxLength(1000); + Property(x => x.ProgressMessage).HasMaxLength(1000); + + HasRequired(x => x.ScheduleTask) + .WithMany(x => x.ScheduleTaskHistory) + .HasForeignKey(x => x.ScheduleTaskId); + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskMap.cs b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskMap.cs index 522c68515b..c3854ec515 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Tasks/ScheduleTaskMap.cs @@ -7,18 +7,15 @@ public partial class ScheduleTaskMap : EntityTypeConfiguration { public ScheduleTaskMap() { - this.ToTable("ScheduleTask"); - this.HasKey(t => t.Id); - this.Property(t => t.Name).HasMaxLength(500).IsRequired(); - this.Property(t => t.Type).HasMaxLength(800).IsRequired(); - this.Property(t => t.Alias).HasMaxLength(500); - this.Property(t => t.LastError).HasMaxLength(1000); - this.Property(t => t.ProgressMessage).HasMaxLength(1000).IsOptional(); - this.Property(t => t.CronExpression).HasMaxLength(1000); - this.Property(t => t.RowVersion).IsConcurrencyToken(); + ToTable("ScheduleTask"); + HasKey(t => t.Id); + Property(t => t.Name).HasMaxLength(500).IsRequired(); + Property(t => t.Type).HasMaxLength(800).IsRequired(); + Property(t => t.Alias).HasMaxLength(500); + Property(t => t.CronExpression).HasMaxLength(1000); - this.Ignore(t => t.IsRunning); - this.Ignore(t => t.IsPending); + Ignore(t => t.IsPending); + Ignore(t => t.LastHistoryEntry); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Mapping/Topics/TopicMap.cs b/src/Libraries/SmartStore.Data/Mapping/Topics/TopicMap.cs index 7c3bf5dec2..4e2af6ab58 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Topics/TopicMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Topics/TopicMap.cs @@ -9,7 +9,10 @@ public TopicMap() { this.ToTable("Topic"); this.HasKey(t => t.Id); + this.Property(t => t.ShortTitle).HasMaxLength(50); + this.Property(t => t.Intro).HasMaxLength(255); this.Property(t => t.Body).IsMaxLength(); - } + //this.Property(t => t.IsPublished).HasColumnAnnotation("defaultValue", true); + } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs index 23b0f56a14..58b0e21624 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201504171629262_V22Final.cs @@ -71,7 +71,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Next", "Next", "Weiter"); - builder.AddOrUpdate("Admin.Common.BackToConfiguration", "Back to configuration", "Zur�ck zur Konfiguration"); @@ -90,7 +89,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.UnknownError", "An unknown error has occurred.", "Es ist ein unbekannter Fehler aufgetreten."); - builder.AddOrUpdate("Plugins.Feed.FreeShippingThreshold", "Free shipping threshold", "Kostenloser Versand ab", diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs index a1ddb8c90a..640f6fca9e 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs @@ -11,13 +11,21 @@ public partial class log4net : DbMigration, ILocaleResourcesProvider, IDataSeede { public override void Up() { - // Custom START - if (DataSettings.Current.IsSqlServer) - { - //DropIndex("dbo.Log", "IX_Log_ContentHash"); - Sql("IF EXISTS (SELECT * FROM sys.indexes WHERE name='IX_Log_ContentHash' AND object_id = OBJECT_ID('[dbo].[Log]')) DROP INDEX [IX_Log_ContentHash] ON [dbo].[Log];"); - Sql(@"Truncate Table [Log]"); - } + // Custom START + if (HostingEnvironment.IsHosted) + { + if (DataSettings.Current.IsSqlServer) + { + Sql("IF EXISTS (SELECT * FROM sys.indexes WHERE name='IX_Log_ContentHash' AND object_id = OBJECT_ID('[dbo].[Log]')) DROP INDEX [IX_Log_ContentHash] ON [dbo].[Log];"); + Sql(@"TRUNCATE Table [Log]"); + } + else + { + Sql(@"SET LOCK_TIMEOUT 20000;"); + DropIndex("Log", "IX_Log_ContentHash"); + Sql(@"DELETE FROM Log;"); + } + } // Custom END AddColumn("dbo.Log", "Logger", c => c.String(nullable: false, maxLength: 400)); diff --git a/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs index fdc42d0c31..79b6bd0ffa 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201705102339006_V3Final.cs @@ -1,4 +1,4 @@ -namespace SmartStore.Data.Migrations +namespace SmartStore.Data.Migrations { using System; using System.Data.Entity; @@ -173,25 +173,25 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Badge text", "Badge-Text", "Gets or sets the text of the badge which will be displayed next to the category link within menus.", - "Legt den Text der Badge fest, die innerhalb von Menus neben den Menueintr�gen dargestellt wird."); + "Legt den Text der Badge fest, die innerhalb von Menus neben den Menueinträgen dargestellt wird."); builder.AddOrUpdate("Admin.Catalog.Categories.Fields.BadgeStyle", "Badge style", "Badge-Style", "Gets or sets the type of the badge which will be displayed next to the category link within menus.", - "Legt den Stil der Badge fest, die innerhalb von Menus neben den Menueintr�gen dargestellt wird."); + "Legt den Stil der Badge fest, die innerhalb von Menus neben den Menueinträgen dargestellt wird."); builder.AddOrUpdate("Admin.Header.ClearDbCache", "Clear database cache", - "Datenbank Cache l�schen"); + "Datenbank Cache löschen"); builder.AddOrUpdate("Admin.System.Warnings.TaskScheduler.OK", "The task scheduler can poll and execute tasks.", - "Der Task-Scheduler kann Hintergrund-Aufgaben planen und ausf�hren."); + "Der Task-Scheduler kann Hintergrund-Aufgaben planen und ausführen."); builder.AddOrUpdate("Admin.System.Warnings.TaskScheduler.Fail", "The task scheduler cannot poll and execute tasks. Base URL: {0}, Status: {1}. Please specify a working base url in web.config, setting 'sm:TaskSchedulerBaseUrl'.", - "Der Task-Scheduler kann keine Hintergrund-Aufgaben planen und ausf�hren. Basis-URL: {0}, Status: {1}. Bitte legen Sie eine vom Webserver erreichbare Basis-URL in der web.config Datei fest, Einstellung: 'sm:TaskSchedulerBaseUrl'."); + "Der Task-Scheduler kann keine Hintergrund-Aufgaben planen und ausführen. Basis-URL: {0}, Status: {1}. Bitte legen Sie eine vom Webserver erreichbare Basis-URL in der web.config Datei fest, Einstellung: 'sm:TaskSchedulerBaseUrl'."); builder.AddOrUpdate("Products.NotFound", "The product with ID {0} was not found.", @@ -199,7 +199,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Products.Deleted", "The product with ID {0} has been deleted.", - "Das Produkt mit der ID {0} wurde gel�scht."); + "Das Produkt mit der ID {0} wurde gelöscht."); builder.AddOrUpdate("Common.ShowLess", "Show less", "Weniger anzeigen"); builder.AddOrUpdate("Menu.ServiceMenu", "Help & Services", "Hilfe & Service"); @@ -210,7 +210,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search", "Search", "Suchen"); builder.AddOrUpdate("Search.Title", "Search", "Suche"); - builder.AddOrUpdate("Search.PageTitle", "Search result for {0}", "Suchergebnis f�r {0}"); + builder.AddOrUpdate("Search.PageTitle", "Search result for {0}", "Suchergebnis für {0}"); builder.AddOrUpdate("Search.PagingInfo", "{0} of {1}", "{0} von {1}"); builder.AddOrUpdate("Search.DidYouMean", "Did you mean?", "Meinten Sie?"); builder.AddOrUpdate("Search.Hits", "Hits", "Treffer"); @@ -225,10 +225,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.SearchBox.Tooltip", "What are you looking for?", "Wonach suchen Sie?"); builder.AddOrUpdate("Search.SearchTermMinimumLengthIsNCharacters", "The minimum length for the search term is {0} characters.", - "Die Mindestl�nge f�r den Suchbegriff betr�gt {0} Zeichen."); + "Die Mindestlänge für den Suchbegriff beträgt {0} Zeichen."); builder.AddOrUpdate("Search.TermCorrectedHint", "Displaying results for {0}. Your search for {1} did not match any results.", - "Ergebnisse f�r {0} werden angezeigt. Ihre Suche nach {1} ergab leider keine Treffer."); + "Ergebnisse für {0} werden angezeigt. Ihre Suche nach {1} ergab leider keine Treffer."); builder.Delete("Enums.SmartStore.Core.Domain.Catalog.ProductSortingEnum.Position"); @@ -258,7 +258,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Enable Instant Search", "Instant-Suche aktivieren", "Activates Instant Search (Search-As-You-Type). Search hits and suggestions are already displayed before user finishes typing the search term.", - "Aktiviert die Instant-Suche (Search-As-You-Type). Suchtreffer und -Vorschl�ge werden schon w�hrend der Eingabe des Suchbegriffs angezeigt."); + "Aktiviert die Instant-Suche (Search-As-You-Type). Suchtreffer und -Vorschläge werden schon während der Eingabe des Suchbegriffs angezeigt."); builder.AddOrUpdate("Admin.Configuration.Settings.Search.ShowProductImagesInInstantSearch", "Show product images", @@ -274,15 +274,15 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchTermMinLength", "Minimum search term length", - "Minimale Suchbegriffl�nge", + "Minimale Suchbegrifflänge", "Specifies the minimum length of a search term from which to show the result of instant search.", - "Legt die minimale L�nge eines Suchbegriffs fest, ab dem das Ergebnis der Instantsuche angezeigt wird."); + "Legt die minimale Länge eines Suchbegriffs fest, ab dem das Ergebnis der Instantsuche angezeigt wird."); builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFields", "Search fields", "Suchfelder", "Specifies additional search fields. The product name is always searched.", - "Legt zus�tzlich zu durchsuchende Felder fest. Der Produktname wird grunds�tzlich immer durchsucht."); + "Legt zusätzlich zu durchsuchende Felder fest. Der Produktname wird grundsätzlich immer durchsucht."); builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DefaultProductListPageSize", "Number of products displayed per page", @@ -296,14 +296,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Validation.ValueGreaterThan", "The value must be greater than {0}.", - "Der Wert muss gr��er als {0} sein."); + "Der Wert muss größer als {0} sein."); builder.AddOrUpdate("Admin.Validation.InvalidPath", "The path \"{0}\" is invalid. Please enter a valid path.", - "Der Pfad \"{0}\" ist ung�ltig. Bitte geben Sie einen g�ltigen Pfad ein."); + "Der Pfad \"{0}\" ist ungültig. Bitte geben Sie einen gültigen Pfad ein."); builder.AddOrUpdate("Common.AdditionalShippingSurcharge", - "zzgl. {0} zus�tzlicher Versandgeb�hr", + "zzgl. {0} zusätzlicher Versandgebühr", "Plus {0} shipping surcharge"); builder.DeleteFor("Admin.Configuration.ContentSlider"); @@ -327,7 +327,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Show brand logo instead of name", "Zeige Marken-Logo statt -Name", "Specifies whether the brand logo should be displayed in line style product lists. Falls back to textual name if no logo has been uploaded.", - "Legt fest, ob das Marken-Logo in Produktlisten dargestellt werden soll (nicht anwendbar in Rasteransicht). Wenn kein Logo hochgeladen wurde, wird grunds�tzlich der Name angezeigt."); + "Legt fest, ob das Marken-Logo in Produktlisten dargestellt werden soll (nicht anwendbar in Rasteransicht). Wenn kein Logo hochgeladen wurde, wird grundsätzlich der Name angezeigt."); builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowProductOptionsInLists", "Show variant names in product lists", @@ -345,9 +345,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions", "Page size options", - "Auswahlm�glichkeiten f�r Seitengr��e", + "Auswahlmöglichkeiten für Seitengröße", "Comma-separated page size options that a customer can select in product lists.", - "Kommagetrennte Liste mit Optionen f�r Seitengr��e, die ein Kunde in Produktlisten w�hlen kann."); + "Kommagetrennte Liste mit Optionen für Seitengröße, die ein Kunde in Produktlisten wählen kann."); builder.AddOrUpdate("Common.ListIsEmpty", "The list is empty.", "Die Liste ist leer."); @@ -368,9 +368,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Pager.PageXOfY", "Page {0} of {1}", "Seite {0} von {1}"); builder.AddOrUpdate("Pager.PageXOfYShort", "{0} of {1}", "{0} von {1}"); - builder.AddOrUpdate("Products.Price.OldPrice", "Regular", "Regul�r"); + builder.AddOrUpdate("Products.Price.OldPrice", "Regular", "Regulär"); builder.AddOrUpdate("Products.Sku", "SKU", "Art.-Nr."); - builder.AddOrUpdate("Products.ChooseColorX", "Choose {0}", "{0} ausw�hlen"); + builder.AddOrUpdate("Products.ChooseColorX", "Choose {0}", "{0} auswählen"); builder.AddOrUpdate("Tax.LegalInfoShort", "Prices {0}, plus shipping", "Preise {0}, zzgl. Versandkosten"); @@ -382,21 +382,21 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", "The wildcard mode can slow down the search for a large number of products.", - "Der Wildcard-Modus kann bei einer gro�en Anzahl an Produkten die Suche verlangsamen."); + "Der Wildcard-Modus kann bei einer großen Anzahl an Produkten die Suche verlangsamen."); builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFieldsNote", "The standard search supports the search fields Name, SKU and Short Description. For more fields, a search plugin like MegaSearch Plugin is required.", - "In der Standardsuche k�nnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. F�r weitere Felder ist ein Such-Plugin wie bspw. dem MegaSearch Plugin notwendig."); + "In der Standardsuche können die Felder Name, SKU und Kurzbeschreibung durchsucht werden. Für weitere Felder ist ein Such-Plugin wie bspw. dem MegaSearch Plugin notwendig."); builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchMode", "Search mode", "Suchmodus", "Specifies the search mode. Please keep in mind that the search mode can - depending on catalog size - strongly affect search performance. 'Is equal to' is the fastest, 'Contains' the slowest.", - "Legt den Suchmodus fest. Bitte beachten Sie, dass der Suchmodus die Geschwindigkeit der Suche (abh�ngig von der Produktanzahl) beeinflusst. 'Ist gleich' ist am schnellsten, 'Beinhaltet' am langsamsten."); + "Legt den Suchmodus fest. Bitte beachten Sie, dass der Suchmodus die Geschwindigkeit der Suche (abhängig von der Produktanzahl) beeinflusst. 'Ist gleich' ist am schnellsten, 'Beinhaltet' am langsamsten."); builder.AddOrUpdate("Admin.Configuration.DeliveryTimes.CannotDeleteAssignedProducts", "The delivery time cannot be deleted. It has associated products or product variants.", - "Die Lieferzeit kann nicht gel�scht werden. Ihr sind Produkte oder Produktvarianten zugeordnet."); + "Die Lieferzeit kann nicht gelöscht werden. Ihr sind Produkte oder Produktvarianten zugeordnet."); builder.AddOrUpdate("Media.Manufacturer.ImageLinkTitleFormat", "All products from {0}", "Alle Produkte von {0}"); builder.AddOrUpdate("Manufacturers.List", "All Brands", "Alle Marken"); @@ -419,7 +419,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Sort manufacturers alphabetically", "Hersteller alphabetisch sortieren", "Specifies whether manufacturers on the manufacturer overview page will be displayed sorted alphabetically.", - "Legt fest ob Hersteller auf der Hersteller�bersichtsseite alphabetisch sortiert dargestellt werden."); + "Legt fest ob Hersteller auf der Herstellerübersichtsseite alphabetisch sortiert dargestellt werden."); builder.AddOrUpdate("Common.NoImageAvail", "No image available", "Bild wird nachgereicht"); @@ -431,7 +431,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Products.Details", "Description", "Beschreibung"); builder.AddOrUpdate("Products.Specs", "Features", "Merkmale"); builder.AddOrUpdate("Products.Availability.InStockWithQuantity", "{0} in stock", "{0} am Lager"); - builder.AddOrUpdate("Products.Availability.InStock", "In stock", "Vorr�tig"); + builder.AddOrUpdate("Products.Availability.InStock", "In stock", "Vorrätig"); builder.AddOrUpdate("Products.Availability.OutOfStock", "Out of stock", "Vergriffen"); builder.AddOrUpdate("Products.NewProducts", "What's New", "Neu eingetroffen"); @@ -446,7 +446,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Search.FilterMinHitCount", "Minimum hit count for filters", - "Minimale Trefferanzahl f�r Filter", + "Minimale Trefferanzahl für Filter", "Specifies the minimum number of search hits from which to show a filter.", "Legt die minimale Anzahl an Suchtreffern fest, ab dem ein Filter angezeigt wird."); @@ -458,7 +458,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.HitsDesc", "Hit count: highest first", - "Trefferanzahl: H�chste zuerst"); + "Trefferanzahl: Höchste zuerst"); builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.ValueAsc", "Name: A to Z", @@ -466,14 +466,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.DisplayOrder", "According to display order", - "Gem�� Reihenfolge"); + "Gemäß Reihenfolge"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink", "Edit Options (Total: {0})", "Optionen bearbeiten (Anzahl: {0})"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.EditAttributeDetails", "Options for attribute '{0}'. Product: {1}", - "Optionen f�r Attribut '{0}'. Produkt: {1}"); + "Optionen für Attribut '{0}'. Produkt: {1}"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values", "Options", "Optionen"); builder.AddOrUpdate("Admin.Catalog.Attributes.CheckoutAttributes.Values", "Options", "Optionen"); @@ -496,19 +496,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Show default delivery time", "Zeige Standard-Lieferzeit", "Specifies whether to show the default delivery time if there is none assigned to a product.", - "Bestimmt ob die Standard-Lieferzeit f�r ein Produkt angezeigt wird, wenn dem Produkt keine Lieferzeit zugewiesen wurde."); + "Bestimmt ob die Standard-Lieferzeit für ein Produkt angezeigt wird, wenn dem Produkt keine Lieferzeit zugewiesen wurde."); builder.AddOrUpdate("Admin.Catalog.Products.Fields.QuantityStep", "Quantity step", "Schrittweite", "Specifies the incremental respectively decremental step on usage of +/-. Orderable quantities are limited to a multiple of this value.", - "Bestimmt den Wert, um den die Bestellmenge erh�ht bzw. vermindert wird, wenn ein Kunde die +/- Steuerelemente benutzt. Die Bestellmenge ist auf ein Vielfaches dieses Wertes beschr�nkt."); + "Bestimmt den Wert, um den die Bestellmenge erhöht bzw. vermindert wird, wenn ein Kunde die +/- Steuerelemente benutzt. Die Bestellmenge ist auf ein Vielfaches dieses Wertes beschränkt."); builder.AddOrUpdate("Admin.Catalog.Products.Fields.QuantiyControlType", "Control type", "Steuerelement", "Specifies the control type to enter the quantity.", - "Bestimmt das Steuerelement f�r die Angabe der Bestellmenge."); + "Bestimmt das Steuerelement für die Angabe der Bestellmenge."); builder.AddOrUpdate("Admin.Catalog.Products.Fields.HideQuantityControl", "Hide quantity control on product pages", @@ -540,7 +540,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("ShoppingCart.AddToWishlist", "Add to wishlist", "Auf die Wunschliste"); builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToCart", "The product {0} has been successfully added to your cart", "Das Produkt {0} wurde erfolgreich in den Warenkorb gelegt"); builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToWishlist", "The product {0} has been added to your wishlist", "Das Produkt {0} wurde erfolgreich auf ihrer Wunschliste vermerkt"); - builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToCompare", "The product {0} has been successfully added to your compare list", "Das Produkt {0} wurde der Vergleichsliste erfolgreich hinzugef�gt"); + builder.AddOrUpdate("ShoppingCart.Mini.AddedItemToCompare", "The product {0} has been successfully added to your compare list", "Das Produkt {0} wurde der Vergleichsliste erfolgreich hinzugefügt"); builder.AddOrUpdate("ShoppingCart.Mini.EmptyCart.Title", "Shopping cart empty", "Warenkorb ist leer"); builder.AddOrUpdate("ShoppingCart.Mini.EmptyWishlist.Title", "Wishlist empty", "Wunschliste ist leer"); builder.AddOrUpdate("ShoppingCart.Mini.EmptyCompare.Title", "Compare list empty", "Vergleichsliste ist leer"); @@ -558,18 +558,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("ShoppingCart.DiscountCouponCode", "I have a discount code", "Ich habe einen Rabattcode"); builder.AddOrUpdate("ShoppingCart.GiftCardCouponCode", "I have a gift card", "Ich habe einen Gutschein"); - builder.AddOrUpdate("ShoppingCart.EstimateShipping", "Estimate shipping", "Versandkosten sch�tzen"); + builder.AddOrUpdate("ShoppingCart.EstimateShipping", "Estimate shipping", "Versandkosten schätzen"); - builder.AddOrUpdate("PageTitle.Blog.Month", "Blog entries in {0}", "Blog Eintr�ge des Monats {0}"); - builder.AddOrUpdate("PageTitle.Blog.Tag", "Blog entries for the tag {0}", "Blog-Eintr�ge f�r das Stichwort {0}"); - builder.AddOrUpdate("Metadesc.Blog.Month", "Blog entries in {0}", "Blog Eintr�ge des Monats {0}"); - builder.AddOrUpdate("Metadesc.Blog.Tag", "Blog entries for the tag {0}", "Blog-Eintr�ge f�r das Stichwort {0}"); + builder.AddOrUpdate("PageTitle.Blog.Month", "Blog entries in {0}", "Blog Einträge des Monats {0}"); + builder.AddOrUpdate("PageTitle.Blog.Tag", "Blog entries for the tag {0}", "Blog-Einträge für das Stichwort {0}"); + builder.AddOrUpdate("Metadesc.Blog.Month", "Blog entries in {0}", "Blog Einträge des Monats {0}"); + builder.AddOrUpdate("Metadesc.Blog.Tag", "Blog entries for the tag {0}", "Blog-Einträge für das Stichwort {0}"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Picture", "Picture", "Bild", "Choose a picture which will be displayed as the selector for the attribute.", - "W�hlen Sie ein Bild, welches als Auswahlelement f�r das Attribut angezeigt werden soll."); + "Wählen Sie ein Bild, welches als Auswahlelement für das Attribut angezeigt werden soll."); builder.Delete( "Admin.Configuration.Settings.ShoppingCart.MiniShoppingCartProductNumber", @@ -585,9 +585,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Error.ChooseDifferentValue", "Please choose a different value.", - "Bitte w�hlen Sie einen anderen Wert."); + "Bitte wählen Sie einen anderen Wert."); - builder.AddOrUpdate("Common.Menu", "Menu", "Men�"); + builder.AddOrUpdate("Common.Menu", "Menu", "Menü"); builder.Delete( "Admin.Configuration.Settings.GeneralCommon.FullTextSettings", @@ -607,7 +607,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) ); builder.AddOrUpdate("Common.Options.Count", "Number options", "Anzahl Optionen"); - builder.AddOrUpdate("Common.Options.Add", "Add option", "Option hinzuf�gen"); + builder.AddOrUpdate("Common.Options.Add", "Add option", "Option hinzufügen"); builder.AddOrUpdate("Common.Options.Edit", "Edit option", "Option bearbeiten"); builder.AddOrUpdate("Admin.Validation.RequiredField", "Please enter \"{0}\".", "Bitte \"{0}\" angeben."); @@ -618,11 +618,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptions", "Copy set options", - "Set Optionen �bernehmen"); + "Set Optionen übernehmen"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.CopyOptionsHint", "Would you like to copy all options from set \"{0}\"?", - "M�chten Sie alle Optionen von Set \"{0}\" �bernehmen?"); + "Möchten Sie alle Optionen von Set \"{0}\" übernehmen?"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.AskExistingValues", "What should be done with already existing options?", @@ -630,11 +630,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.MergeExistingValues", "Merge all options", - "Alle Optionen zusammenf�hren"); + "Alle Optionen zusammenführen"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.DeleteExistingValues", "Delete existing options", - "Vorhandene Optionen l�schen"); + "Vorhandene Optionen löschen"); builder.AddOrUpdate("Offcanvas.Menu.Categories", "Categories", "Sortiment"); builder.AddOrUpdate("Offcanvas.Menu.Brands", "Brands", "Marken"); @@ -642,7 +642,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Offcanvas.Menu.ShowCurrentCat", "Show {0}", "{0} anzeigen"); var aliasHintEn = "Seo-compliant URL alias for search filters (optional)."; - var aliasHintDe = "SEO-konformer URL-Alias f�r Suchfilter (optional)."; + var aliasHintDe = "SEO-konformer URL-Alias für Suchfilter (optional)."; builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.Alias", "Alias", "Alias", aliasHintEn, aliasHintDe); builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Options.Fields.Alias", "Alias", "Alias", aliasHintEn, aliasHintDe); @@ -679,7 +679,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.Facet.Price", "Price", "Preis"); builder.AddOrUpdate("Search.Facet.Rating", "Rating", "Bewertung"); builder.AddOrUpdate("Search.Facet.DeliveryTime", "Delivery Time", "Lieferzeit"); - builder.AddOrUpdate("Search.Facet.Availability", "Availability", "Verf�gbarkeit"); + builder.AddOrUpdate("Search.Facet.Availability", "Availability", "Verfügbarkeit"); builder.AddOrUpdate("Search.Facet.NewArrivals", "New Arrivals", "Neuheiten"); builder.AddOrUpdate("Search.Facet.RangeMin", "from {0}", "ab {0}"); @@ -687,7 +687,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.Facet.RangeBetween", "{0} - {1}", "{0} - {1}"); builder.AddOrUpdate("Search.Facet.FindPlaceholder", "Find {0}...", "{0} suchen..."); - builder.AddOrUpdate("Search.Facet.SelectedCount", "{0} selected", "{0} ausgew�hlt"); + builder.AddOrUpdate("Search.Facet.SelectedCount", "{0} selected", "{0} ausgewählt"); builder.AddOrUpdate("Search.Facet.RemoveAllFilters", "Remove all filters", "Alle Filter aufheben"); builder.AddOrUpdate("Search.Facet.RemoveFilter", "Remove filter: {0} > {1}", "Filter aufheben: {0} > {1}"); builder.AddOrUpdate("Search.Facet.RemoveGroupFilters", "Remove filters", "Filter aufheben"); @@ -695,7 +695,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.Facet.XStarsAndMore", "{0} stars & more", "{0} Sterne & mehr"); builder.AddOrUpdate("Search.Facet.StarsAndMore", "& more", "& mehr"); builder.AddOrUpdate("Search.Facet.LastDays", "Last {0} days", "Letzten {0} Tage"); - builder.AddOrUpdate("Search.Facet.IncludeOutOfStock", "Include Out of Stock", "Nicht verf�gbare Artikel einschlie�en"); + builder.AddOrUpdate("Search.Facet.IncludeOutOfStock", "Include Out of Stock", "Nicht verfügbare Artikel einschließen"); builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.ForceSslForAllPages", "Always use SSL", @@ -705,16 +705,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.Checkboxes", "Checkboxes", - "Kontrollk�stchen"); + "Kontrollkästchen"); builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.Custom", "Boxes (color & image)", - "K�stchen (Farbe & Bild)"); + "Kästchen (Farbe & Bild)"); builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.NumericRange", "Numeric range", "Numerischer Bereich"); var megaSearchPlusHintEn = "This setting is only effective by using the 'MegaSearchPlus' plugin. Changes will take effect after next update of the search index."; - var megaSearchPlusHintDe = "Diese Einstellung ist nur unter Verwendung des 'MegaSearchPlus' Plugins wirksam. �nderungen werden nach der n�chsten Aktualisierung des Suchindex wirksam."; + var megaSearchPlusHintDe = "Diese Einstellung ist nur unter Verwendung des 'MegaSearchPlus' Plugins wirksam. Änderungen werden nach der nächsten Aktualisierung des Suchindex wirksam."; builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.FacetSorting", "Sorting of search filters", @@ -730,21 +730,21 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.AllowFiltering", "Allow filtering", - "Filtern erm�glichen", + "Filtern ermöglichen", "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, - "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden k�nnen. " + megaSearchPlusHintDe); + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); builder.AddOrUpdate("Admin.Catalog.Products.SpecificationAttributes.Fields.AllowFiltering", "Allow filtering", "Filtern zulassen", "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, - "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden k�nnen. " + megaSearchPlusHintDe); + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.AllowFiltering", "Allow filtering", "Filtern zulassen", "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, - "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden k�nnen. " + megaSearchPlusHintDe); + "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden können. " + megaSearchPlusHintDe); builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.FacetTemplateHint", "Search filter UI type", @@ -756,7 +756,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Numeric value", "Numerischer Wert", "Specifies a numeric value to enbale range filtering (e.g. light red to dark red). \"Numeric range\" must be specified as search filter presentation for the attribute. " + megaSearchPlusHintEn, - "Legt einen numerischen Wert fest, um eine Bereichsfilterung zu erm�glichen (z.B. hellrot bis dunkelrot). F�r das Attribut muss \"Numerischer Bereich\" als Suchfilterdarstellung festgelegt sein. " + megaSearchPlusHintDe); + "Legt einen numerischen Wert fest, um eine Bereichsfilterung zu ermöglichen (z.B. hellrot bis dunkelrot). Für das Attribut muss \"Numerischer Bereich\" als Suchfilterdarstellung festgelegt sein. " + megaSearchPlusHintDe); builder.AddOrUpdate("Admin.Catalog.Attributes.SpecificationAttributes.Fields.ShowOnProductPage", "Show on product page", @@ -767,7 +767,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Account.Administration", "Admin", "Admin"); - builder.AddOrUpdate("Account.PasswordRecovery", "Reset password", "Passwort zur�cksetzen"); + builder.AddOrUpdate("Account.PasswordRecovery", "Reset password", "Passwort zurücksetzen"); builder.AddOrUpdate("Common.Shopbar.BasketPartOne", "Shopping", "Waren"); @@ -788,9 +788,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.ContentManagement.Polls.Fields.SystemKeyword", "System keyword", - "System-Schl�sselwort", + "System-Schlüsselwort", "The system keyword specifies the place in your shop where the poll will be displayed. Available system keywords are: MyAccountMenu, Blog", - "Das System-Schl�sselwort bestimmt den Platz im Shop, an welchem die Umfrage dargestellt wird. Verf�gbare System-Schl�sselw�rter sind: MyAccountMenu, Blog"); + "Das System-Schlüsselwort bestimmt den Platz im Shop, an welchem die Umfrage dargestellt wird. Verfügbare System-Schlüsselwörter sind: MyAccountMenu, Blog"); builder.Delete("Admin.Configuration.Settings.Catalog.ManufacturersBlockItemsToDisplay"); @@ -798,8 +798,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Homepage.Brands.ShowAll", "Show all", "Alle anzeigen"); builder.AddOrUpdate("Account.Fields.ZipPostalCode", "Zip code", "PLZ"); - builder.AddOrUpdate("Account.CustomerReturnRequests.Reason", "Return reason", "R�cksendegrund"); - builder.AddOrUpdate("Account.CustomerReturnRequests.Action", "Return action", "R�cksendeaktion"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Reason", "Return reason", "Rücksendegrund"); + builder.AddOrUpdate("Account.CustomerReturnRequests.Action", "Return action", "Rücksendeaktion"); builder.AddOrUpdate("Account.CustomerReturnRequests.Date", "Date Requested", "Anfragedatum"); builder.AddOrUpdate("Account.CustomerReturnRequests.Item", "Item", "Artikel"); @@ -845,8 +845,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Share-Button Widget-Code", @"Specifies the code to render the share button widget. By going to addthis.com you can create your own widget code and paste it here. This way you can configure the display type of the widget as well as get statistic insight.", - @"Legt den Code des Share-Button Widgets fest. Gehen Sie zu addthis.com um Ihren eigenen Widget-Code zu erhalten und f�gen Sie diesen hier ein. - Auf diese Weise k�nnen Sie die Darstellung des Widgets selbst bestimmen, sowie Statistiken einsehen."); + @"Legt den Code des Share-Button Widgets fest. Gehen Sie zu addthis.com um Ihren eigenen Widget-Code zu erhalten und fügen Sie diesen hier ein. + Auf diese Weise können Sie die Darstellung des Widgets selbst bestimmen, sowie Statistiken einsehen."); builder.AddOrUpdate("Order.CannotCancel") .Value("de", "Die Bestellung kann nicht storniert werden."); @@ -857,13 +857,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Order.CannotMarkPaid") .Value("de", "Die Bestellung kann nicht als bezahlt markiert werden."); builder.AddOrUpdate("Order.CannotPartialRefund") - .Value("de", "Eine Teilr�ckerstattung ist f�r diese Bestellung nicht m�glich."); + .Value("de", "Eine Teilrückerstattung ist für diese Bestellung nicht möglich."); builder.AddOrUpdate("Order.CannotRefund") - .Value("de", "Eine R�ckerstattung ist f�r diese Bestellung nicht m�glich."); + .Value("de", "Eine Rückerstattung ist für diese Bestellung nicht möglich."); builder.AddOrUpdate("Order.CannotVoid") - .Value("de", "Eine Stornierung dieser Bestellung ist nicht m�glich."); + .Value("de", "Eine Stornierung dieser Bestellung ist nicht möglich."); builder.AddOrUpdate("Order.CompletePayment.Hint") - .Value("de", "Die Bestellung wurde noch nicht bezahlt. Um die Zahlung nun vorzunehmen, klicken Sie die Schaltfl�che 'Zahlung veranlassen'"); + .Value("de", "Die Bestellung wurde noch nicht bezahlt. Um die Zahlung nun vorzunehmen, klicken Sie die Schaltfläche 'Zahlung veranlassen'"); builder.AddOrUpdate("Order.getpdfinvoice") .Value("de", "Bestellung als PDF"); builder.AddOrUpdate("Order.NotFound") @@ -889,7 +889,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Account.CustomerOrders.NotYourOrder") .Value("de", "Diese Bestellung konnte Ihnen nicht zugeordnet werden."); builder.AddOrUpdate("Account.CustomerOrders.RecurringOrders.InitialOrder") - .Value("de", "Urspr�ngliche Bestellung"); + .Value("de", "Ursprüngliche Bestellung"); builder.AddOrUpdate("Account.CustomerOrders.RecurringOrders.ViewInitialOrder") .Value("de", "Bestellungsansicht (ID - {0})"); @@ -905,14 +905,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Search.IndexingRequiredNotification", "This is the default search. For advanced search, indexing is required. Now start indexing or open configuration.", - "Hierbei handelt es sich um die Standardsuche. F�r die erweiterte Suche ist eine Indexierung erforderlich. Indexierung jetzt starten oder Konfiguration aufrufen."); + "Hierbei handelt es sich um die Standardsuche. Für die erweiterte Suche ist eine Indexierung erforderlich. Indexierung jetzt starten oder Konfiguration aufrufen."); builder.Delete("ShoppingCart.UpdateCartItem", "ShoppingCart.UpdateCart"); builder.AddOrUpdate("ShoppingCart.SKU", "SKU", "Art.-Nr."); builder.AddOrUpdate("Products.ProductsHaveBeenAddedToTheCart", "The selected products have successfully been added to the cart.", - "Die von Ihnen gew�hlten Produkte wurden in den Warenkorb gelegt."); + "Die von Ihnen gewählten Produkte wurden in den Warenkorb gelegt."); builder.AddOrUpdate("Forum.TopicSubject", "Topic subject", @@ -922,7 +922,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Themes.Notifications.ConfigureError", "SASS CSS Parser Error: Your changes were not saved because your configuration would lead to an error in the shop. For details see report.", - "SASS CSS Parser Fehler: Ihre �nderungen wurden nicht gespeichert, da Ihre Konfiguration zu einem Fehler im Shop f�hren w�rde. Details siehe Fehlerbericht."); + "SASS CSS Parser Fehler: Ihre Änderungen wurden nicht gespeichert, da Ihre Konfiguration zu einem Fehler im Shop führen würde. Details siehe Fehlerbericht."); builder.AddOrUpdate("Admin.Configuration.Themes.Validation.ErrorReportTitle", "SASS parser error report", "SASS Parser Fehlerbericht"); @@ -930,7 +930,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete("Enums.SmartStore.Core.Domain.Catalog.AttributeControlType.ColorSquares"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.AttributeControlType.Boxes", "Boxes (color & image)", - "K�stchen (Farbe & Bild)"); + "Kästchen (Farbe & Bild)"); builder.Delete( "Admin.Themes.Grid", @@ -946,7 +946,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Order.CompletePayment.AdminNote", "The payment is pending. The buyer can make the payment by clicking on the Complete payment button on the order details page.", - "Die Zahlung ist ausstehend. Der K�ufer kann die Zahlung durchf�hren, indem er auf der Bestelldetailseite den Button Zahlung veranlassen klickt."); + "Die Zahlung ist ausstehend. Der Käufer kann die Zahlung durchführen, indem er auf der Bestelldetailseite den Button Zahlung veranlassen klickt."); builder.AddOrUpdate("Admin.ThemeVar.CostepPogressColor", "Specifies the color of the checkout progress bar.", @@ -973,18 +973,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) @"

Um Google Fonts einzubinden, beachten Sie bitte die nachfolgende Anweisung.

  • - Gehen Sie zu https://fonts.google.com/ und w�hlen Sie die Schriftarten, die Sie in Ihrem Shop verwenden m�chten. + Gehen Sie zu https://fonts.google.com/ und wählen Sie die Schriftarten, die Sie in Ihrem Shop verwenden möchten.
  • - Als Html-Code f�r Ihre Webseite, wird Ihnen ein Link in folgender Form angeboten: + Als Html-Code für Ihre Webseite, wird Ihnen ein Link in folgender Form angeboten:
    <link href=""https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700|Ubuntu"" rel=""stylesheet"">
  • - F�gen Sie den fett markierten Teil des Links in eins der drei Felder des Theme-Konfigurators ein, die f�r Google Fonts vorgesehen sind. + Fügen Sie den fett markierten Teil des Links in eins der drei Felder des Theme-Konfigurators ein, die für Google Fonts vorgesehen sind.
  • - Nun k�nnen Sie die Schriftart verwenden, indem Sie den Namen der Schriftart (z.B. Roboto) in den Eingabefeldern angeben, - die f�r Schriftarten vorgesehen sind (z.B. $font-family-sans-serif). + Nun können Sie die Schriftart verwenden, indem Sie den Namen der Schriftart (z.B. Roboto) in den Eingabefeldern angeben, + die für Schriftarten vorgesehen sind (z.B. $font-family-sans-serif).
"); @@ -1007,13 +1007,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.ThemeVar.Boxed", "Specifies whether the site will strech over the complete avaliable space.", - "Legt fest, ob sich die Seite �ber den kompletten verf�gabren Platz streckt."); + "Legt fest, ob sich die Seite über den kompletten verfügabren Platz streckt."); builder.AddOrUpdate("Admin.ThemeVar.ArtActiveBgColor", "Specifies the background color for product boxes when hovering over them.", - "Legt die Hintergrundfarbe von Produktboxen f�r den Hover-Effekt fest."); + "Legt die Hintergrundfarbe von Produktboxen für den Hover-Effekt fest."); builder.AddOrUpdate("Admin.ThemeVar.ArtActiveBorderColor", "Specifies the border color for product boxes when hovering over them.", - "Legt die Rahmenfarbe von Produktboxen f�r den Hover-Effekt fest."); + "Legt die Rahmenfarbe von Produktboxen für den Hover-Effekt fest."); builder.AddOrUpdate("Content.CopyrightNotice", "Copyright © {0} {1}. All rights reserved.", "Copyright © {0} {1}. Alle Rechte vorbehalten."); diff --git a/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs index 7e84e3cbb0..b4f74fd90f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201706020759565_UpdateMediaPath.cs @@ -1,4 +1,4 @@ -namespace SmartStore.Data.Migrations +namespace SmartStore.Data.Migrations { using System.Data.Entity.Migrations; using System.Web.Hosting; @@ -60,12 +60,12 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { - builder.AddOrUpdate("Common.For", "For: {0}", "F�r: {0}"); + builder.AddOrUpdate("Common.For", "For: {0}", "Für: {0}"); builder.AddOrUpdate("Products.Sorting.Featured", "Featured", "Empfehlung"); builder.AddOrUpdate("Common.AdditionalShippingSurcharge", "Plus {0} shipping surcharge", - "zzgl. {0} zus�tzlicher Versandgeb�hr"); + "zzgl. {0} zusätzlicher Versandgebühr"); builder.AddOrUpdate("Address.Fields.Salutation", "Salutation", "Anrede"); builder.AddOrUpdate("Address.Fields.Title", "Title", "Titel"); @@ -75,7 +75,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Salutations", "Anreden", "Comma separated list of salutations (e.g. Mr., Mrs). Define the entries which will populate the dropdown list for salutation when entering addresses.", - "Komma getrennte Liste (z.B. Herr, Frau). Bestimmen Sie die Eintr�ge f�r die Auswahl der Anrede, bei der Adresserfassung."); + "Komma getrennte Liste (z.B. Herr, Frau). Bestimmen Sie die Einträge für die Auswahl der Anrede, bei der Adresserfassung."); builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.SalutationEnabled", "'Salutation' enabled", @@ -97,17 +97,17 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.SkipShippingIfSingleOption", "Display shipping options during checkout process only if more then one option is available", - "Versandartauswahl nur anzeigen, wenn mehr als eine Versandart zur Verf�gung steht", + "Versandartauswahl nur anzeigen, wenn mehr als eine Versandart zur Verfügung steht", "Display shipping options during the checkout process only if more then one shipping option is available.", - "Legt fest, ob die Versandartauswahl nur im Checkout-Prozess angezeigt wird, wenn mehr als eine Versandart zur Verf�gung steht"); + "Legt fest, ob die Versandartauswahl nur im Checkout-Prozess angezeigt wird, wenn mehr als eine Versandart zur Verfügung steht"); builder.AddOrUpdate("Admin.DataExchange.Export.Projection.OnlyIndividuallyVisibleAssociated", "Only individually visible products", "Nur individuell sichtbare Produkte", "Specifies whether to only export individually visible associated products.", - "Legt fest, ob nur individuell sichtbare, verkn�pfte Produkte exportiert werden sollen."); + "Legt fest, ob nur individuell sichtbare, verknüpfte Produkte exportiert werden sollen."); - builder.AddOrUpdate("Product.ThumbTitle", "{0}, Picture {1} large", "{0}, Bild {1} gro�"); + builder.AddOrUpdate("Product.ThumbTitle", "{0}, Picture {1} large", "{0}, Bild {1} groß"); builder.AddOrUpdate("Product.ThumbAlternateText", "{0}, Picture {1}", "{0}, Bild {1}"); } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs index eb8c6e194b..70488ff6a7 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201708251628482_SystemTopics.cs @@ -43,15 +43,15 @@ public void Seed(SmartObjectContext context) var topics = context.Set().Where(x => systemTopics.Contains(x.SystemName)).ToList(); topics.Each(x => x.IsSystemTopic = true); - context.SaveChanges(); + context.MigrateLocaleResources(MigrateLocaleResources); - context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Admin.ContentManagement.Topics.CannotBeDeleted", - "This topic is needed by your Shop and can therefore not be deleted.", + "This topic is required by your Shop and can therefore not be deleted.", "Diese Seite wird von Ihrem Shop ben�tigt und kann daher nicht gel�scht werden."); } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs index c8dd751181..547f68afe8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201711222311112_MoveFsMedia.cs @@ -3,16 +3,11 @@ namespace SmartStore.Data.Migrations using System; using System.Data.Entity.Migrations; using System.Web.Hosting; - using System.Linq; - using System.Collections.Generic; - using SmartStore.Core.Domain.Configuration; using SmartStore.Data.Setup; using SmartStore.Utilities; - using Core.Infrastructure; - using Core.IO; - using System.Text.RegularExpressions; using System.IO; using Core.Data; + using SmartStore.Data.Utilities; public partial class MoveFsMedia : DbMigration, IDataSeeder { @@ -39,58 +34,8 @@ public void Seed(SmartObjectContext context) // Move the whole media folder to new location at first MoveMediaFolder(); - // Check whether FS storage provider is active... - var setting = context.Set().FirstOrDefault(x => x.Name == "Media.Storage.Provider"); - if (setting == null || !setting.Value.IsCaseInsensitiveEqual("MediaStorage.SmartStoreFileSystem")) - { - // DB provider is active: no need to move anything. - return; - } - - // What a huge, fucking hack! > IMediaFileSystem is defined in an - // assembly which we don't reference from here. But it also implements - // IFileSystem, which we can cast to. - var fsType = Type.GetType("SmartStore.Services.Media.IMediaFileSystem, SmartStore.Services"); - var fs = EngineContext.Current.Resolve(fsType) as IFileSystem; - - // Pattern for file matching. E.g. matches 0000234-0.png - var rg = new Regex(@"^([0-9]{7})-0[.](.{3,4})$", RegexOptions.Compiled | RegexOptions.Singleline); - - var subfolders = new Dictionary(); - - // Get root files - var files = fs.ListFiles("").ToList(); - foreach (var chunk in files.Slice(500)) - { - foreach (var file in chunk) - { - var match = rg.Match(file.Name); - if (match.Success) - { - var name = match.Groups[1].Value; - var ext = match.Groups[2].Value; - // The new file name without trailing -0 - var newName = string.Concat(name, ".", ext); - // The subfolder name, e.g. 0024, when file name is 0024893.png - var dirName = name.Substring(0, DirMaxLength); - - string subfolder = null; - if (!subfolders.TryGetValue(dirName, out subfolder)) - { - // Create subfolder "Storage/0000" - subfolder = fs.Combine("Storage", dirName); - fs.TryCreateFolder(subfolder); - subfolders[dirName] = subfolder; - } - - // Build destination path - var destinationPath = fs.Combine(subfolder, newName); - - // Move the file now! - fs.RenameFile(file.Path, destinationPath); - } - } - } + // Reorganize files (root > Storage/{subfolder}) + DataMigrator.MoveFsMedia(context); } private void MoveMediaFolder() diff --git a/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs index 4799f3aecd..a2184757a0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201711291017168_SyncStringResources.cs @@ -83,7 +83,7 @@ public void Seed(SmartObjectContext context) var allLanguages = context.Set().ToList(); // Accidents. - var accidents = resourceSet.Where(x => x.ResourceName == "Admin.Configuration.ActivityLog.ActivityLogTy pe").ToList(); + var accidents = resourceSet.Where(x => x.ResourceName == "Admin.Configuration.ActivityLog.ActivityLogType").ToList(); if (accidents.Any()) { accidents.Each(x => x.ResourceName = "Admin.Configuration.ActivityLog.ActivityLogType"); diff --git a/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.Designer.cs new file mode 100644 index 0000000000..665cb9c0b9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ExportAttributeMappings : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportAttributeMappings)); + + string IMigrationMetadata.Id + { + get { return "201802270844034_ExportAttributeMappings"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.cs b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.cs new file mode 100644 index 0000000000..d99106df13 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ExportAttributeMappings : DbMigration + { + public override void Up() + { + AddColumn("dbo.ProductAttribute", "ExportMappings", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ProductAttribute", "ExportMappings"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.resx b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.resx new file mode 100644 index 0000000000..cf1434b570 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201802270844034_ExportAttributeMappings.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.Designer.cs new file mode 100644 index 0000000000..35a2953735 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Wallet : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Wallet)); + + string IMigrationMetadata.Id + { + get { return "201804060721031_Wallet"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.cs b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.cs new file mode 100644 index 0000000000..634e0f249f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.cs @@ -0,0 +1,105 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + using Setup; + + public partial class Wallet : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.WalletHistory", + c => new + { + Id = c.Int(nullable: false, identity: true), + StoreId = c.Int(nullable: false), + CustomerId = c.Int(nullable: false), + OrderId = c.Int(), + Amount = c.Decimal(nullable: false, precision: 18, scale: 4), + AmountBalance = c.Decimal(nullable: false, precision: 18, scale: 4), + AmountBalancePerStore = c.Decimal(nullable: false, precision: 18, scale: 4), + CreatedOnUtc = c.DateTime(nullable: false), + Reason = c.Int(), + Message = c.String(maxLength: 1000), + AdminComment = c.String(maxLength: 4000), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.Customer", t => t.CustomerId, cascadeDelete: true) + .ForeignKey("dbo.Order", t => t.OrderId) + .Index(t => new { t.StoreId, t.CreatedOnUtc }, name: "IX_StoreId_CreatedOn") + .Index(t => t.CustomerId) + .Index(t => t.OrderId); + + AddColumn("dbo.Product", "IsSystemProduct", c => c.Boolean(nullable: false)); + AddColumn("dbo.Product", "SystemName", c => c.String(maxLength: 500)); + AddColumn("dbo.Order", "CreditBalance", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + AddColumn("dbo.Order", "RefundedCreditBalance", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + CreateIndex("dbo.Product", new[] { "SystemName", "IsSystemProduct" }, name: "Product_SystemName_IsSystemProduct"); + } + + public override void Down() + { + DropForeignKey("dbo.WalletHistory", "OrderId", "dbo.Order"); + DropForeignKey("dbo.WalletHistory", "CustomerId", "dbo.Customer"); + DropIndex("dbo.WalletHistory", new[] { "OrderId" }); + DropIndex("dbo.WalletHistory", new[] { "CustomerId" }); + DropIndex("dbo.WalletHistory", "IX_StoreId_CreatedOn"); + DropIndex("dbo.Product", "Product_SystemName_IsSystemProduct"); + DropColumn("dbo.Order", "RefundedCreditBalance"); + DropColumn("dbo.Order", "CreditBalance"); + DropColumn("dbo.Product", "SystemName"); + DropColumn("dbo.Product", "IsSystemProduct"); + DropTable("dbo.WalletHistory"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Admin", + "Administration", + "Administration"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Purchase", + "Purchase", + "Einkauf"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Refill", + "Refilling", + "Auff�llung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Refund", + "Refund", + "R�ckerstattung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.PartialRefund", + "Partial refund", + "Teilerstattung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Debit", + "Debit", + "Kontobelastung"); + + builder.AddOrUpdate("ShoppingCart.Totals.CreditBalance", + "Credit balance", + "Guthaben"); + + builder.AddOrUpdate("Admin.Orders.Fields.CreditBalance", + "Credit balance", + "Guthaben", + "The used credit balance.", + "Das verwendete Guthaben."); + + builder.AddOrUpdate("Admin.Validation.ValueLessThan", + "The value must be less than {0}.", + "Der Wert muss kleiner {0} sein."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.resx b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.resx new file mode 100644 index 0000000000..ed0573d84b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804060721031_Wallet.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.Designer.cs new file mode 100644 index 0000000000..b6bd4f6aae --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ForceSslForAllPages : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ForceSslForAllPages)); + + string IMigrationMetadata.Id + { + get { return "201804090744324_ForceSslForAllPages"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.cs b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.cs new file mode 100644 index 0000000000..d0835dc7e8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.cs @@ -0,0 +1,84 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Linq; + using SmartStore.Core.Domain.Configuration; + using SmartStore.Core.Domain.Stores; + using SmartStore.Data.Setup; + + public partial class ForceSslForAllPages : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + DropIndex("dbo.Product", "Product_SystemName_IsSystemProduct"); + AddColumn("dbo.Store", "ForceSslForAllPages", c => c.Boolean(nullable: false)); + AlterColumn("dbo.Product", "SystemName", c => c.String(maxLength: 400)); + CreateIndex("dbo.Product", new[] { "SystemName", "IsSystemProduct" }, name: "Product_SystemName_IsSystemProduct"); + DropColumn("dbo.Order", "RefundedCreditBalance"); + } + + public override void Down() + { + AddColumn("dbo.Order", "RefundedCreditBalance", c => c.Decimal(nullable: false, precision: 18, scale: 4)); + DropIndex("dbo.Product", "Product_SystemName_IsSystemProduct"); + AlterColumn("dbo.Product", "SystemName", c => c.String(maxLength: 500)); + DropColumn("dbo.Store", "ForceSslForAllPages"); + CreateIndex("dbo.Product", new[] { "SystemName", "IsSystemProduct" }, name: "Product_SystemName_IsSystemProduct"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + try + { + var stores = context.Set().ToList(); + var settings = context.Set(); + // Do not use a dictionary because of duplicates. + var sslSettings = settings + .Where(x => x.Name == "SecuritySettings.ForceSslForAllPages") + .ToList(); + var defaultSetting = sslSettings.FirstOrDefault(x => x.StoreId == 0); + + foreach (var store in stores) + { + var setting = sslSettings.FirstOrDefault(x => x.StoreId == store.Id); + if (setting != null) + { + store.ForceSslForAllPages = setting.Value.ToBool(true); + } + else if (defaultSetting != null) + { + store.ForceSslForAllPages = defaultSetting.Value.ToBool(true); + } + else + { + store.ForceSslForAllPages = false; + } + } + + // Remove settings because they are not used anymore. + settings.RemoveRange(sslSettings); + } + catch { } + + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete("Admin.Configuration.Settings.GeneralCommon.ForceSslForAllPages"); + + builder.AddOrUpdate("Admin.Configuration.Stores.Fields.ForceSslForAllPages", + "Always use SSL", + "Immer SSL verwenden", + "Specifies whether to SSL secure all request.", + "Legt fest, dass alle Anfragen SSL gesichert werden sollen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.resx b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.resx new file mode 100644 index 0000000000..d6e0f7edd5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804090744324_ForceSslForAllPages.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.Designer.cs new file mode 100644 index 0000000000..fc477fb5ce --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class V310Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V310Resources)); + + string IMigrationMetadata.Id + { + get { return "201804200835273_V310Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.cs new file mode 100644 index 0000000000..ff3a2d3b7b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.cs @@ -0,0 +1,394 @@ +namespace SmartStore.Data.Migrations +{ + using SmartStore.Core.Domain.Configuration; + using SmartStore.Core.Domain.Customers; + using SmartStore.Core.Domain.Media; + using SmartStore.Data.Setup; + using SmartStore.Utilities; + using System; + using System.Data.Entity.Migrations; + using System.Linq; + + public partial class V310Resources : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + + } + + public override void Down() + { + + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + MigrateSettings(context); + + context.SaveChanges(); + } + + public void MigrateSettings(SmartObjectContext context) + { + // Change MediaSettings.MaximumImageSize to 2048 + var name = TypeHelper.NameOf(y => y.MaximumImageSize, true); + var setting = context.Set().FirstOrDefault(x => x.Name == name); + if (setting != null && setting.Value.Convert() < 2048) + { + setting.Value = "2048"; + } + + // Change MediaSettings.AvatarPictureSize to 250 + name = TypeHelper.NameOf(y => y.AvatarPictureSize, true); + setting = context.Set().FirstOrDefault(x => x.Name == name); + if (setting != null && setting.Value.Convert() < 250) + { + setting.Value = "250"; + } + + // Change MediaSettings.AvatarMaximumSizeBytes to 512000 (500 KB) + name = TypeHelper.NameOf(y => y.AvatarMaximumSizeBytes, true); + setting = context.Set().FirstOrDefault(x => x.Name == name); + if (setting != null && setting.Value.Convert() < 512000) + { + setting.Value = "512000"; + } + + // Delete MessageTemplatesSettings + var settings = context.Set(); + var caseInvariantReplacementSetting = settings.FirstOrDefault(x => x.Name == "MessageTemplatesSettings.CaseInvariantReplacement"); + var color1Setting = settings.FirstOrDefault(x => x.Name == "MessageTemplatesSettings.Color1"); + var color2Setting = settings.FirstOrDefault(x => x.Name == "MessageTemplatesSettings.Color2"); + var color3Setting = settings.FirstOrDefault(x => x.Name == "MessageTemplatesSettings.Color3"); + + if (caseInvariantReplacementSetting != null) settings.Remove(caseInvariantReplacementSetting); + if (color1Setting != null) settings.Remove(color1Setting); + if (color2Setting != null) settings.Remove(color2Setting); + if (color3Setting != null) settings.Remove(color3Setting); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Orders.Shipment", "Shipment", "Lieferung"); + builder.AddOrUpdate("Admin.Order", "Order", "Auftrag"); + + builder.AddOrUpdate("Admin.Order.ViaShippingMethod", "via {0}", "via {0}"); + builder.AddOrUpdate("Admin.Order.WithPaymentMethod", "with {0}", "per {0}"); + builder.AddOrUpdate("Admin.Order.FromStore", "from {0}", "von {0}"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.MaxItemsToDisplayInCatalogMenu", + "Max items to display in catalog menu", + "Maximale Anzahl von Elementen im Katalogmen�", + "Defines the maximum number of top level items to be displayed in the main catalog menu. All menu items which are exceeding this limit will be placed in a new dropdown menu item.", + "Legt die maximale Anzahl von Menu-Eintr�gen der obersten Hierarchie fest, die im Katalogmen� angezeigt werden. Alle weiteren Menu-Eintr�ge werden innerhalb eines neuen Dropdownmenus ausgegeben."); + + builder.AddOrUpdate("CatalogMenu.MoreLink", "More", "Mehr"); + + builder.AddOrUpdate("Admin.CatalogSettings.Homepage", "Homepage", "Homepage"); + builder.AddOrUpdate("Admin.CatalogSettings.ProductDisplay", "Product display", "Produktdarstellung"); + builder.AddOrUpdate("Admin.CatalogSettings.Prices", "Prices", "Preise"); + builder.AddOrUpdate("Admin.CatalogSettings.CompareProducts", "Compare products", "Produktvergleich"); + + builder.AddOrUpdate("Footer.Service.Mobile", "Service", "Service, Versand & Zahlung"); + builder.AddOrUpdate("Footer.Company.Mobile", "Company", "Firma, Impressum & Datenschutz"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.LabelAsc", + "Displayed Name: A to Z", + "Angezeigter Name: A bis Z"); + + builder.AddOrUpdate("Admin.Catalog.Products.Copy.NumberOfCopies", + "Number of copies", + "Anzahl an Kopien", + "Defines the number of copies to be created.", + "Legt die Anzahl der anzulegenden Kopien fest."); + + builder.AddOrUpdate("Admin.Configuration.Languages.OfType", + "of type \"{0}\"", + "vom Typ \"{0}\""); + + builder.AddOrUpdate("Admin.Configuration.Languages.CheckAvailableLanguagesFailed", + "An error occurred while checking for other available languages.", + "Bei der Suche nach weiteren verf�gbaren Sprachen trat ein Fehler auf."); + + builder.AddOrUpdate("Admin.Configuration.Languages.NoAvailableLanguagesFound", + "There were no other available languages found for version {0}. On translate.smartstore.com you will find more details about available resources.", + "Es wurden keine weiteren verf�gbaren Sprachen f�r Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verf�gbaren Ressourcen."); + + builder.AddOrUpdate("Admin.Configuration.Languages.InstalledLanguages", + "Installed Languages", + "Installierte Sprachen"); + builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages", + "Available Languages", + "Verf�gbare Sprachen"); + + builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages.Note", + "Click Download to install a new language including all localized resources. On translate.smartstore.com you will find more details about available resources.", + "Klicken Sie auf Download, um eine neue Sprache mit allen lokalisierten Ressourcen zu installieren. Auf translate.smartstore.com finden Sie weitere Details zu verf�gbaren Ressourcen."); + + builder.AddOrUpdate("Common.Translated", + "Translated", + "�bersetzt"); + builder.AddOrUpdate("Admin.Configuration.Languages.TranslatedPercentage", + "{0}% translated", + "{0}% �bersetzt"); + builder.AddOrUpdate("Admin.Configuration.Languages.TranslatedPercentageAtLastImport", + "{0}% at the last import", + "{0}% beim letzten Import"); + + builder.AddOrUpdate("Admin.Configuration.Languages.NumberOfTranslatedResources", + "{0} of {1}", + "{0} von {1}"); + + builder.AddOrUpdate("Admin.Configuration.Languages.DownloadingResources", + "Loading ressources", + "Lade Ressourcen"); + builder.AddOrUpdate("Admin.Configuration.Languages.ImportResources", + "Import resources", + "Importiere Ressourcen"); + + builder.AddOrUpdate("Admin.Configuration.Languages.OnePublishedLanguageRequired", + "At least one published language is required.", + "Mindestens eine ver�ffentlichte Sprache ist erforderlich."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.AvailableLanguageSetId", + "Available Languages", + "Verf�gbare Sprachen", + "Specifies the available language whose localized resources are to be imported.", + "Legt die verf�gbare Sprache fest, deren lokalisierte Ressourcen importiert werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Languages.UploadFileOrSelectLanguage", + "Please upload an import file or select an available language whose resources are to be imported.", + "Bitte laden Sie eine Importdatei hoch oder w�hlen Sie eine verf�gbare Sprache, deren Ressourcen importiert werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.ChargeOnlyHighestProductShippingSurcharge", + "Charge the highest shipping surcharge only", + "Nur den h�chsten Transportzuschlag berechnen", + "Specifies whether to charge only the highest additional shipping surcharge of products.", + "Bestimmt ob bei der Berechnung der Versandkosten nur der h�chste Transportzuschlag von Produkten ber�cksichtigt wird."); + + builder.AddOrUpdate("Order.OrderDetails") + .Value("en", "Order Details"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Media.AutoGenerateAbsoluteUrls", + "Generate absolute URLs", + "Absolute URLs erzeugen", + "Generates absolute URLs for media files by prepending the current host name (e.g. http://myshop.com/media/image/1.jpg instead of /media/image/1.jpg). Has no effect if a CDN URL has been applied to the store.", + "Erzeugt absolute URLs f�r Mediendateien, indem der aktuelle Hostname vorangestellt wird (z.B. http://meinshop.de/media/image/1.jpg statt /media/image/1.jpg). Hat keine Auswirkung, wenn f�r den Store eine CDN-URL eingerichtet wurde."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFieldsNote", + "The Name, SKU and Short Description fields can be searched in the standard search. Other fields require a search plugin such as the MegaSearch plugin from Premium Edition.", + "In der Standardsuche k�nnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. F�r weitere Felder ist ein Such-Plugin wie etwa das MegaSearch-Plugin aus der Premium Edition notwendig."); + + builder.AddOrUpdate("Admin.DataExchange.Import.FolderName", "Folder path", "Ordnerpfad"); + + builder.AddOrUpdate("Admin.MessageTemplate.Preview.From", "From", "Von"); + builder.AddOrUpdate("Admin.MessageTemplate.Preview.To", "To", "An"); + builder.AddOrUpdate("Admin.MessageTemplate.Preview.ReplyTo", "Reply To", "Antwort an"); + builder.AddOrUpdate("Admin.MessageTemplate.Preview.SendTestMail", "Test-E-mail to...", "Test E-Mail an..."); + builder.AddOrUpdate("Admin.MessageTemplate.Preview.TestMailSent", "E-mail has been sent.", "E-Mail gesendet."); + builder.AddOrUpdate("Admin.MessageTemplate.Preview.NoBody", + "The generated preview file seems to have expired. Please reload the page.", + "Die generierte Vorschaudatei scheint abgelaufen zu sein. Laden Sie die Seite bitte neu."); + + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Preview.SuccessfullySent", + "The email has been sent successfully.", + "Die E-Mail wurde erfolgreich versendet."); + + builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.SuccessfullyCopied", + "The message template has been copied successfully.", + "Die Nachrichtenvorlage wurde erfolgreich kopiert."); + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.ShoppingCartItem", "Shopping Cart", "Warenkorb"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.ShoppingCartType.ShoppingCart", "Shopping Cart", "Warenkorb"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.ShoppingCartType.Wishlist", "Wishlist", "Wunschliste"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NoBundleProducts", + "Do not export bundled products", + "Keine Produkt-Bundle exportieren", + "Specifies whether to export bundled products. If this option is activated, then the associated bundle items will be exported.", + "Legt fest, ob Produkt-Bundle exportiert werden sollen. Ist diese Option aktiviert, so werden die zum Bundle geh�renden Produkte (Bundle-Bestandteile) exportiert."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ShoppingCartTypeId", + "Shopping cart type", + "Warenkorbtyp", + "Filter by shopping cart type.", + "Nach Warenkorbtyp filtern."); + + builder.AddOrUpdate("Common.CustomerId", "Customer ID", "Kunden ID"); + + builder.AddOrUpdate("Account.AccountActivation.InvalidEmailOrToken", + "Unknown email or token. Please register again.", + "Unbekannte E-Mail oder Token. Bitte f�hren Sie die Registrierung erneut durch."); + + builder.AddOrUpdate("Account.PasswordRecoveryConfirm.InvalidEmailOrToken", + "Unknown email or token. Please click \"Forgot password\" again, if you want to renew your password.", + "Unbekannte E-Mail oder Token. Klicken Sie bitte erneut \"Passwort vergessen\", falls Sie Ihr Passwort erneuern m�chten."); + + builder.Delete("Account.PasswordRecoveryConfirm.InvalidEmail"); + builder.Delete("Account.PasswordRecoveryConfirm.InvalidToken"); + + builder.AddOrUpdate("Admin.Common.Acl.SubjectTo", + "Restrict access", + "Zugriff einschr�nken", + "Determines whether this entity is subject to access restrictions (no = no restriction, yes = accessible only for selected customer groups)", + "Legt fest, ob dieser Datensatz Zugriffsbeschr�nkungen unterliegt (Nein = keine Beschr�nkung, Ja = zug�nglich nur f�r gew�hlte Kundengruppen)"); + + builder.AddOrUpdate("Admin.Common.Acl.AvailableFor", + "Customer roles", + "Kundengruppen", + "Select customer roles who can access the entity. For all inactive roles, this record is hidden.", + "W�hlen Sie Kundengruppen, die auf den Datensatz zugreifen k�nnen. Bei allen nicht aktivierten Gruppen wird dieser Datensatz ausgeblendet."); + + builder.Delete( + "Admin.Catalog.Categories.Fields.SubjectToAcl", + "Admin.Catalog.Categories.Fields.AclCustomerRoles", + "Admin.Catalog.Products.Fields.SubjectToAcl", + "Admin.Catalog.Products.Fields.AclCustomerRoles", + "Common.Options.Count"); + + builder.AddOrUpdate("Admin.Common.ApplyFilter", "Apply filter", "Filter anwenden"); + builder.AddOrUpdate("Time.Milliseconds", "Milliseconds", "Millisekunden"); + builder.AddOrUpdate("Common.Pixel", "Pixel", "Pixel"); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ShowPlaceholder", "Show placeholder", "Zeige Platzhalter"); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.HidePlaceholder", "Hide placeholder", "Verberge Platzhalter"); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.UpdateExampleFileName", "Update example", "Aktualisiere Beispiel"); + + builder.AddOrUpdate("Admin.Configuration.Themes.AvailableDesktopThemes", "Installed themes", "Installierte Themes"); + + builder.AddOrUpdate("Admin.Catalog.Products.List.GoDirectlyToSku", "Find by SKU", "Nach SKU suchen"); + builder.AddOrUpdate("Admin.Orders.List.GoDirectlyToNumber", "Find by order id", "Nach Auftragsnummer suchen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.StoreLastIpAddress", + "Store IP address", + "IP-Adresse speichern", + "Specifies whether to store the IP address in the customer data set.", + "Legt fest, ob die IP-Adresse im Kundendatensatz gespeichert werden soll."); + + builder.AddOrUpdate("Admin.Orders.Info", "General", "Allgemein"); + builder.AddOrUpdate("Admin.Orders.BillingAndShipment", "Billing & Shipping", "Rechnung & Versand"); + builder.AddOrUpdate("Admin.Orders.Fields.ShippingAddress.ViewOnGoogleMaps", "View on Google Maps", "Auf Google Maps ansehen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.SocialSettings.InstagramLink", + "Instagram Link", + "Instagram Link", + "Leave this field empty if the Instagram link should not be shown", + "Lassen Sie dieses Feld leer, wenn der Instagram Link nicht angezeigt werden soll"); + + builder.AddOrUpdate("Common.License", "License", "Lizenz"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Payments.CapturePaymentReason.OrderShipped", + "The order has been marked as shipped", + "Der Auftrag wurde als versendet markiert"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Payments.CapturePaymentReason.OrderDelivered", + "The order has been marked as delivered", + "Der Auftrag wurde als ausgeliefert markiert"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Payment.CapturePaymentReason", + "Capture payment amount when�", + "Zahlungsbetrag einziehen, wenn�", + "Specifies the event when the payment amount is automatically captured. The selected payment method must support capturing for this.", + "Legt das Ereignis fest, zu dem der Zahlunsgbetrag automatisch eingezogen wird. Die gew�hlte Zahlart muss hierf�r Buchungen unterst�tzen."); + + #region taken from V22Final, because they were never added yet + + builder.AddOrUpdate("Common.Next", + "Next", + "Weiter"); + builder.AddOrUpdate("Admin.Common.BackToConfiguration", + "Back to configuration", + "Zur�ck zur Konfiguration"); + builder.AddOrUpdate("Admin.Common.UploadFileSucceeded", + "The file has been successfully uploaded.", + "Die Datei wurde erfolgreich hochgeladen."); + builder.AddOrUpdate("Admin.Common.UploadFileFailed", + "The upload has failed.", + "Der Upload ist leider fehlgeschlagen."); + builder.AddOrUpdate("Admin.Common.ImportAll", + "Import all", + "Alle importieren"); + builder.AddOrUpdate("Admin.Common.ImportSelected", + "Import selected", + "Ausgew�hlte importieren"); + builder.AddOrUpdate("Admin.Common.UnknownError", + "An unknown error has occurred.", + "Es ist ein unbekannter Fehler aufgetreten."); + builder.AddOrUpdate("Plugins.Feed.FreeShippingThreshold", + "Free shipping threshold", + "Kostenloser Versand ab", + "Amount as from shipping is free.", + "Betrag, ab dem keine Versandkosten anfallen."); + + #endregion + + builder.AddOrUpdate("Admin.Product.Picture.Added", + "The picture has successfully been added", + "Das Bild wurde erfolgreich zugef�gt"); + + builder.AddOrUpdate("HtmlEditor.ClickToEdit", "Click to edit HTML...", "Hier klicken, um HTML zu editieren..."); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.ExportMappings.Note", + "Define mappings of attribute values to export fields according to the pattern <Format prefix>:<Export field name>. Example: gmc:color exports the attribute values for colors to the field color during the Google Merchant Center Export. The mappings are only effective when exporting attribute combinations.", + "Legen Sie Zuordnungen von Attributwerten zu Exportfeldern nach dem Muster <Formatpr�fix>:<Export-Feldname> fest. Beispiel: gmc:color exportiert beim Google Merchant Center Export die Attributwerte f�r Farben in das Feld color. Die Zuordnungen sind nur beim Export von Attributkombinationen wirksam."); + + builder.AddOrUpdate("Admin.Catalog.Attributes.ProductAttributes.Fields.ExportMappings", + "Mappings to export fields", + "Zuordnungen zu Exportfeldern", + "Allows to map attribute values to export fields. Each entry has to be entered in a new line.", + "Erm�glicht die Zuordnung von Attributwerten zu Exportfeldern. Jeder Eintrag muss in einer neuen Zeile erfolgen."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.AdditionalFee", + "Additional fee", + "Zus�tzliche Geb�hr", + "Specifies an additional fee to be charged to the customer for using the payment method.", + "Legt eine zus�tzliche Geb�hr fest, die dem Kunden f�r die Inanspruchnahme der Zahlart berechnet wird."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.AdditionalFeePercentage", + "Additional fee percentage", + "Zus�tzliche Geb�hr prozentual", + "Specifies whether the additional fee should be calculated as a percentage. A fixed value is used if this option is disabled.", + "Legt fest, ob die zus�tzliche Geb�hr prozentual berechnet werden soll. Es wird ein fester Wert verwendet, falls diese Option deaktiviert ist."); + + builder.Delete("Common.Buttons.Default"); + builder.AddOrUpdate("Common.Buttons.Secondary", "Secondary", "Secondary"); + builder.AddOrUpdate("Common.Buttons.Light", "Light", "Light"); + builder.AddOrUpdate("Common.Buttons.Dark", "Dark", "Dark"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.CountryRequired", + "'Country' required", + "Die Eingabe eines Landes ist erforderlich", + "Check the box if 'Country' is required.", + "Legt fest, ob die Eingabe eines Landes erforderlich ist."); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.StateProvinceRequired", + "'State/province' required", + "Die Eingabe eines Bundeslandes ist erforderlich", + "Check the box if 'State/province' is required.", + "Legt fest, ob die Eingabe eines Bundeslandes erforderlich ist."); + + builder.AddOrUpdate("Address.Fields.StateProvince.Required", "State is required.", "Bundesland wird ben�tigt"); + + builder.AddOrUpdate("Common.Columns", "Columns", "Spalten"); + builder.AddOrUpdate("Common.Mru", "Recently", "Zuletzt"); + + builder.AddOrUpdate("Admin.WidgetZones.UserDefined", "User-defined", "Benutzerdefiniert"); + + builder.AddOrUpdate("Admin.Configuration.ManageLanguages", "Manage languages", "Sprachen verwalten"); + + builder.AddOrUpdate("Admin.Customers.Customers.Info", "General", "Allgemein"); + builder.AddOrUpdate("Admin.Customers.Customers.Impersonate", "Impersonate", "Imitieren"); + builder.AddOrUpdate("Admin.Customers.Customers.CurrentCart", "Current cart", "Aktueller Warenkorb"); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.CannotBeDeleted", + "This topic is needed by your Shop and can therefore not be deleted.", + "Diese Seite wird von Ihrem Shop ben�tigt und kann daher nicht gel�scht werden."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.resx new file mode 100644 index 0000000000..1de2ce1652 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804200835273_V310Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.Designer.cs new file mode 100644 index 0000000000..c285369161 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class TopicSlugs : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TopicSlugs)); + + string IMigrationMetadata.Id + { + get { return "201804252356096_TopicSlugs"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.cs b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.cs new file mode 100644 index 0000000000..3eefe926f1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.cs @@ -0,0 +1,70 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Linq; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Topics; + using SmartStore.Data.Setup; + using SmartStore.Core.Domain.Seo; + using SmartStore.Utilities; + + public partial class TopicSlugs : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + var allTopics = context.Set() + .AsNoTracking() + .Select(x => new { x.Id, x.SystemName, x.Title }) + .ToList(); + + var urlRecords = context.Set(); + + foreach (var topic in allTopics) + { + var slug = SeoHelper.GetSeName(topic.SystemName, true, false).Truncate(400); + int i = 2; + var tempSlug = slug; + + while (urlRecords.Any(x => x.Slug == tempSlug)) + { + tempSlug = string.Format("{0}-{1}", slug, i); + i++; + } + + slug = tempSlug; + + var ur = urlRecords.FirstOrDefault(x => x.LanguageId == 0 && x.EntityName == "Topic" && x.EntityId == topic.Id); + if (ur != null) + { + ur.Slug = slug; + } + else + { + urlRecords.Add(new UrlRecord + { + EntityId = topic.Id, + EntityName = "Topic", + IsActive = true, + LanguageId = 0, + Slug = slug + }); + } + + context.SaveChanges(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.resx b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.resx new file mode 100644 index 0000000000..c00cbff6cb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201804252356096_TopicSlugs.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.Designer.cs new file mode 100644 index 0000000000..6516923338 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class V315Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V315Resources)); + + string IMigrationMetadata.Id + { + get { return "201805250724399_V315Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.cs new file mode 100644 index 0000000000..e1a02d62ce --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.cs @@ -0,0 +1,195 @@ +namespace SmartStore.Data.Migrations +{ + using SmartStore.Core.Domain.Catalog; + using SmartStore.Core.Domain.Configuration; + using SmartStore.Core.Domain.Seo; + using SmartStore.Data.Setup; + using SmartStore.Utilities; + using System.Data.Entity.Migrations; + using System.Linq; + + public partial class V315Resources : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + + MigrateSettings(context); + context.SaveChanges(); + } + + public void MigrateSettings(SmartObjectContext context) + { + // SeoSettings.RedirectLegacyTopicUrls should be true when migrating (it is false by default after fresh install) + var name = TypeHelper.NameOf(y => y.RedirectLegacyTopicUrls, true); + context.MigrateSettings(x => x.Add(name, true)); + + // remove setting which were moved from customer settings to privacy settings and have new default values which should be applied immediately + var settings = context.Set(); + var storeLastIpAddressSetting = settings.FirstOrDefault(x => x.Name == "CustomerSettings.StoreLastIpAddress"); + if (storeLastIpAddressSetting != null) settings.Remove(storeLastIpAddressSetting); + + var displayPrivacyAgreementOnContactUs = settings.FirstOrDefault(x => x.Name == "CustomerSettings.DisplayPrivacyAgreementOnContactUs"); + if (displayPrivacyAgreementOnContactUs != null) settings.Remove(displayPrivacyAgreementOnContactUs); + + + var showShareButtonName = TypeHelper.NameOf(y => y.ShowShareButton, true); + var showShareButtonSetting = context.Set().FirstOrDefault(x => x.Name == showShareButtonName); + if (showShareButtonSetting != null) + { + showShareButtonSetting.Value = "False"; + } + + var allowAnonymousUsersToEmailAFriendSetting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.AllowAnonymousUsersToEmailAFriend"); + if (allowAnonymousUsersToEmailAFriendSetting != null) + { + allowAnonymousUsersToEmailAFriendSetting.Value = "False"; + } + + var allowAnonymousUsersToReviewProductSetting = context.Set().FirstOrDefault(x => x.Name == "CatalogSettings.AllowAnonymousUsersToReviewProduct"); + if (allowAnonymousUsersToReviewProductSetting != null) + { + allowAnonymousUsersToReviewProductSetting.Value = "False"; + } + + var allowAnonymousUsersToEmailWishlistSetting = context.Set().FirstOrDefault(x => x.Name == "ShoppingCartSettings.AllowAnonymousUsersToEmailWishlist"); + if (allowAnonymousUsersToEmailWishlistSetting != null) + { + allowAnonymousUsersToEmailWishlistSetting.Value = "False"; + } + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOver.Hint", + "Specifies whether customers can agree to a transferring of their email address to third parties when ordering, and whether the checkbox is enabled by default during checkout. Please note that the 'Show activated' option isn't legally compliant in line with the GDPR.", + "Legt fest, ob Kunden bei einer Bestellung der Weitergabe ihrer E-Mail Adresse an Dritte zustimmen k�nnen und ob die Checkbox daf�r standardm��ig aktiviert ist. Bitte beachten Sie, dass die Option 'Aktiviert anzeigen' im Rahmen der DSVGO nicht rechtskonform ist."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy", "Privacy", "Datenschutz"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.EnableCookieConsent", + "Enable cookie consent", + "Cookie-Hinweis aktivieren", + "Specifies whether the cookie consent box will be displayed in the frontend.", + "Legt fest, ob ein Element f�r die Zustimmung zur Nutzung von Cookies im Frontend angezeigt wird."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.CookieConsentBadgetext", + "Cookie consent display text", + "Cookie-Hinweistext", + "Specifies the text, that will be displayed to your customers if they havn't agreed to the usage of cookis yet.", + "Bestimmt den Text, der Ihren Kunden beim Besuch der Seite angezeigt wird, sofern Sie ihre Zustimmung zur Nutzung von Cookies noch nicht gegeben haben."); + + builder.AddOrUpdate("CookieConsent.BadgeText", + "{0} is using cookies, to guarantee the best shopping experience. Partially cookies will be set by third parties. Privacy Info", + "{0} benutzt Cookies, um Ihnen das beste Einkaufserlebnis zu erm�glichen. Zum Teil werden Cookies auch von Drittanbietern gesetzt. Datenschutzerkl�rung"); + + builder.AddOrUpdate("CookieConsent.Button", "Okay, got it", "Ok, verstanden"); + + builder.Delete("ContactUs.PrivacyAgreement"); + + builder.Delete("Admin.Configuration.Settings.CustomerUser.StoreLastIpAddress"); + builder.Delete("Admin.Configuration.Settings.CustomerUser.StoreLastIpAddress.Hint"); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.StoreLastIpAddress", + "Store IP address", + "IP-Adresse speichern", + "Specifies whether to store the IP address in the customer data set.", + "Legt fest, ob die IP-Adresse im Kundendatensatz gespeichert werden soll."); + + builder.Delete("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs"); + builder.Delete("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs.Hint"); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.DisplayGdprConsentOnForms", + "Get privacy consent for form submissions", + "Einwilligungserkl�rung in Formularen fordern", + "Specifies whether a checkbox is displayed in forms that prompts the user to agree to the processing of his data.", + "Bestimmt ob in Formularen eine Checkbox angezeigt wird, die den Benutzer auffordert der Verarbeitung seiner Daten zuzustimmen."); + + builder.AddOrUpdate("Gdpr.Consent.ValidationMessage", + "Please agree to the processing of your data.", + "Bitte stimmen Sie der Verarbeitung Ihrer Daten zu."); + + builder.Delete("ContactUs.PrivacyAgreement.MustBeAccepted"); + builder.Delete("ContactUs.PrivacyAgreement.DetailText"); + builder.AddOrUpdate("Gdpr.Consent.DetailText", + "Yes I've read the privacy terms and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry.", + "Ja, ich habe die Datenschutzerkl�rung zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden. Meine Daten werden dabei nur zur Bearbeitung meiner Anfrage genutzt."); + + builder.AddOrUpdate("Gdpr.Anonymous", "Anonymous", "Anonym"); + builder.AddOrUpdate("Gdpr.Anonymize", "Anonymize", "Anonymisieren"); + builder.AddOrUpdate("Gdpr.DeletedText", "Deleted", "Gel�scht"); + builder.AddOrUpdate("Gdpr.DeletedLongText", + "This content was deleted by the author.", + "Dieser Inhalt wurde vom Autor gel�scht."); + builder.AddOrUpdate("Gdpr.Anonymize.Success", + "The customer record '{0}' has been anonymized.", + "Der Kundendatensatz '{0}' wurde anonymisiert."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.LastResourcesImportOn", + "Last import", + "Letzter Import", + "The date on which resources were last downloaded and imported.", + "Das Datum, an dem zuletzt Ressourcen heruntergeladen und importiert worden sind."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerFormFields", "Registration", "Registrierung"); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields", "Addresses", "Adressen"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.FirstNameRequired", + "First name required", + "Vorname ist erforderlich", + "Check the box if 'First name' is required.", + "Legt fest, ob die Angabe des Vornamens erforderlich ist."); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.LastNameRequired", + "Last name required", + "Nachname ist erforderlich", + "Check the box if 'Last name' is required.", + "Legt fest, ob die Angabe des Nachnamens erforderlich ist."); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.FullNameOnContactUsRequired", + "Name in the contact form is required", + "Name im Kontaktformular ist erforderlich", + "Specifies whether the name is required in the contact form.", + "Legt fest, ob die Angabe des Namens im Kontaktformular erforderlich ist."); + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.Privacy.FullNameOnProductRequestRequired", + "Name in the product request is required", + "Name im Produktanfrage-Formular ist erforderlich", + "Specifies whether the name is required in the product request form.", + "Legt fest, ob die Angabe des Namens im Produktanfrage-Formular erforderlich ist."); + + builder.AddOrUpdate("Checkout.TermsOfService.IAccept", + "I agree with the {0}terms of service{1} and I adhere to them unconditionally. I've read the {3}privacy terms{1} and agree that my data given by me can be stored electronically.", + "Ich habe {0}die AGB{1} und {2}das Widerrufsrecht{1} gelesen und bin mit der Geltung einverstanden. Ich habe die {3}Datenschutzerkl�rung{1} zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden."); + + builder.AddOrUpdate("Admin.Customers.Customers.List.SearchDeletedOnly", "Only deactivated customers", "Nur deaktivierte Kunden"); + + builder.AddOrUpdate("Admin.Common.Global", "Global", "Global"); + builder.AddOrUpdate("Admin.Common.News", "News", "News"); + builder.AddOrUpdate("Admin.Common.Navigation", "Navigation", "Navigation"); + builder.AddOrUpdate("Admin.Common.PDF", "PDF", "PDF"); + builder.AddOrUpdate("Admin.Common.Footer", "Footer", "Footer"); + + builder.AddOrUpdate("Gdpr.Consent.DetailText.Small", "I agree to the Privacy policy.", "Mit den Bestimmungen zum Datenschutz bin ich einverstanden"); + + builder.AddOrUpdate("Account.Fields.Newsletter", + "I would like to subscribe to the newsletter. I agree to the . Unsubscription is possible at any time.", + "Ich m�chte den Newsletter abonnieren. Mit den Bestimmungen zum Datenschutz bin ich einverstanden. Eine Abmeldung ist jederzeit m�glich."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.EnableHoneypotProtection", + "Enable Honeypot protection", + "Honeypot aktivieren", + "Honeypot is a simple but reliable bot detection method that does not require any captcha. If active, registration and contact forms are protected against bots and attackers.", + "Honeypot ist eine simple aber zuverl�ssige Bot-Erkennungsmethode, die ganz ohne Captcha auskommt. Wenn aktiv, werden Registrierungs- und Kontaktformular vor Bots und Angreifern gesch�tzt."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.resx new file mode 100644 index 0000000000..634083eeb2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201805250724399_V315Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.Designer.cs new file mode 100644 index 0000000000..f041a2400e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class RefundReturnRequests : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(RefundReturnRequests)); + + string IMigrationMetadata.Id + { + get { return "201806051221399_RefundReturnRequests"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.cs b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.cs new file mode 100644 index 0000000000..c887156aea --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class RefundReturnRequests : DbMigration + { + public override void Up() + { + AddColumn("dbo.ReturnRequest", "RefundToWallet", c => c.Boolean()); + } + + public override void Down() + { + DropColumn("dbo.ReturnRequest", "RefundToWallet"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.resx b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.resx new file mode 100644 index 0000000000..02e56e37fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806051221399_RefundReturnRequests.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.Designer.cs new file mode 100644 index 0000000000..4bc2296375 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ScheduleTaskHistory : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ScheduleTaskHistory)); + + string IMigrationMetadata.Id + { + get { return "201806231547270_ScheduleTaskHistory"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs new file mode 100644 index 0000000000..5d558c26db --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs @@ -0,0 +1,99 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Linq; + using SmartStore.Core.Domain.Tasks; + using SmartStore.Data.Setup; + + public partial class ScheduleTaskHistory : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + DropIndex("dbo.ScheduleTask", "IX_LastStart_LastEnd"); + CreateTable( + "dbo.ScheduleTaskHistory", + c => new + { + Id = c.Int(nullable: false, identity: true), + ScheduleTaskId = c.Int(nullable: false), + IsRunning = c.Boolean(nullable: false), + MachineName = c.String(maxLength: 400), + StartedOnUtc = c.DateTime(nullable: false), + FinishedOnUtc = c.DateTime(), + SucceededOnUtc = c.DateTime(), + Error = c.String(maxLength: 1000), + ProgressPercent = c.Int(), + ProgressMessage = c.String(maxLength: 1000), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ScheduleTask", t => t.ScheduleTaskId, cascadeDelete: true) + .Index(t => t.ScheduleTaskId) + .Index(t => new { t.MachineName, t.IsRunning }) + .Index(t => new { t.StartedOnUtc, t.FinishedOnUtc }, name: "IX_Started_Finished"); + + AddColumn("dbo.ScheduleTask", "RunPerMachine", c => c.Boolean(nullable: false)); + DropColumn("dbo.ScheduleTask", "LastStartUtc"); + DropColumn("dbo.ScheduleTask", "LastEndUtc"); + DropColumn("dbo.ScheduleTask", "LastSuccessUtc"); + DropColumn("dbo.ScheduleTask", "LastError"); + DropColumn("dbo.ScheduleTask", "ProgressPercent"); + DropColumn("dbo.ScheduleTask", "ProgressMessage"); + DropColumn("dbo.ScheduleTask", "RowVersion"); + } + + public override void Down() + { + AddColumn("dbo.ScheduleTask", "RowVersion", c => c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion")); + AddColumn("dbo.ScheduleTask", "ProgressMessage", c => c.String(maxLength: 1000)); + AddColumn("dbo.ScheduleTask", "ProgressPercent", c => c.Int()); + AddColumn("dbo.ScheduleTask", "LastError", c => c.String(maxLength: 1000)); + AddColumn("dbo.ScheduleTask", "LastSuccessUtc", c => c.DateTime()); + AddColumn("dbo.ScheduleTask", "LastEndUtc", c => c.DateTime()); + AddColumn("dbo.ScheduleTask", "LastStartUtc", c => c.DateTime()); + DropForeignKey("dbo.ScheduleTaskHistory", "ScheduleTaskId", "dbo.ScheduleTask"); + DropIndex("dbo.ScheduleTaskHistory", "IX_Started_Finished"); + DropIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + DropIndex("dbo.ScheduleTaskHistory", new[] { "ScheduleTaskId" }); + DropColumn("dbo.ScheduleTask", "RunPerMachine"); + DropTable("dbo.ScheduleTaskHistory"); + CreateIndex("dbo.ScheduleTask", new[] { "LastStartUtc", "LastEndUtc" }, name: "IX_LastStart_LastEnd"); + } + + public bool RollbackOnFailure => false; + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + + // Search index task is the first one to run per machine. Better not create a second plugin migration for these few lines. + var taskSet = context.Set(); + var indexingTask = taskSet.FirstOrDefault(x => x.Type == "SmartStore.MegaSearch.IndexingTask, SmartStore.MegaSearch"); + if (indexingTask != null) + { + indexingTask.RunPerMachine = true; + context.SaveChanges(); + } + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.History", "History", "Historie"); + builder.AddOrUpdate("Common.ExecutedOn", "Executed on", "Ausgef�hrt am"); + builder.AddOrUpdate("Common.FinishedOn", "Finished on", "Beendet am"); + builder.AddOrUpdate("Common.Status", "Status", "Status"); + builder.AddOrUpdate("Common.Succeeded", "Succeeded", "Erfolgreich"); + builder.AddOrUpdate("Common.MachineName", "Machine name", "Maschinenname"); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunPerMachine", + "Run per machine", + "Pro Maschine ausf�hren", + "Indicates whether the task is executed decidedly on each machine of a web farm.", + "Gibt an, ob die Aufgabe auf jeder Maschine einer Webfarm dezidiert ausgef�hrt wird."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.HistoryCleanupNote", + "The history is cleaned up once a day: maximum {0} entries and none older than {1} days.", + "Die Historie wird einmal t�glich bereinigt: Maximal {0} Eintr�ge und keine �lter als {1} Tage."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.resx b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.resx new file mode 100644 index 0000000000..ba4c5195f7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.Designer.cs new file mode 100644 index 0000000000..dbfaf98582 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class MoveCustomerFields : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(MoveCustomerFields)); + + string IMigrationMetadata.Id + { + get { return "201807051830375_MoveCustomerFields"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs new file mode 100644 index 0000000000..a780ddb08a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.cs @@ -0,0 +1,69 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + using SmartStore.Data.Utilities; + + public partial class MoveCustomerFields : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.Customer", "Salutation", c => c.String(maxLength: 50)); + AddColumn("dbo.Customer", "Title", c => c.String(maxLength: 100)); + AddColumn("dbo.Customer", "FirstName", c => c.String(maxLength: 225)); + AddColumn("dbo.Customer", "LastName", c => c.String(maxLength: 225)); + AddColumn("dbo.Customer", "FullName", c => c.String(maxLength: 450)); + AddColumn("dbo.Customer", "Company", c => c.String(maxLength: 255)); + AddColumn("dbo.Customer", "CustomerNumber", c => c.String(maxLength: 100)); + AddColumn("dbo.Customer", "BirthDate", c => c.DateTime()); + CreateIndex("dbo.Customer", "FullName", name: "IX_Customer_FullName"); + CreateIndex("dbo.Customer", "Company", name: "IX_Customer_Company"); + CreateIndex("dbo.Customer", "CustomerNumber", name: "IX_Customer_CustomerNumber", unique: false); + CreateIndex("dbo.Customer", "BirthDate", name: "IX_Customer_BirthDate"); + } + + public override void Down() + { + DropIndex("dbo.Customer", "IX_Customer_BirthDate"); + DropIndex("dbo.Customer", "IX_Customer_CustomerNumber"); + DropIndex("dbo.Customer", "IX_Customer_Company"); + DropIndex("dbo.Customer", "IX_Customer_FullName"); + DropColumn("dbo.Customer", "BirthDate"); + DropColumn("dbo.Customer", "CustomerNumber"); + DropColumn("dbo.Customer", "Company"); + DropColumn("dbo.Customer", "FullName"); + DropColumn("dbo.Customer", "LastName"); + DropColumn("dbo.Customer", "FirstName"); + DropColumn("dbo.Customer", "Title"); + DropColumn("dbo.Customer", "Salutation"); + } + + public bool RollbackOnFailure => true; + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + + DataMigrator.MoveCustomerFields(context); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.Delete( + "Admin.Customers.Customers.List.SearchFirstName", + "Admin.Customers.Customers.List.SearchFirstName.Hint", + "Admin.Customers.Customers.List.SearchLastName", + "Admin.Customers.Customers.List.SearchLastName.Hint", + "Admin.Customers.Customers.List.SearchCompany", + "Admin.Customers.Customers.List.SearchCompany.Hint"); + + builder.AddOrUpdate("Admin.Customers.Customers.List.SearchTerm", + "Search term", + "Suchbegriff", + "Name or company", + "Name oder Firma"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.resx b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.resx new file mode 100644 index 0000000000..d161d925e2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807051830375_MoveCustomerFields.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.Designer.cs new file mode 100644 index 0000000000..a646dea18f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class TopicAcl : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TopicAcl)); + + string IMigrationMetadata.Id + { + get { return "201807122120062_TopicAcl"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.cs new file mode 100644 index 0000000000..639c810790 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.cs @@ -0,0 +1,28 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class TopicAcl : DbMigration + { + public override void Up() + { + DropIndex("dbo.Customer", "IX_Customer_CustomerNumber"); + AddColumn("dbo.Topic", "ShortTitle", c => c.String(maxLength: 50)); + AddColumn("dbo.Topic", "Intro", c => c.String(maxLength: 255)); + AddColumn("dbo.Topic", "SubjectToAcl", c => c.Boolean(nullable: false)); + AddColumn("dbo.Topic", "IsPublished", c => c.Boolean(nullable: false, defaultValue: true)); + CreateIndex("dbo.Customer", "CustomerNumber", name: "IX_Customer_CustomerNumber"); + } + + public override void Down() + { + DropIndex("dbo.Customer", "IX_Customer_CustomerNumber"); + DropColumn("dbo.Topic", "IsPublished"); + DropColumn("dbo.Topic", "SubjectToAcl"); + DropColumn("dbo.Topic", "Intro"); + DropColumn("dbo.Topic", "ShortTitle"); + CreateIndex("dbo.Customer", "CustomerNumber", unique: true, name: "IX_Customer_CustomerNumber"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.resx new file mode 100644 index 0000000000..528ddb44b5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807122120062_TopicAcl.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.Designer.cs new file mode 100644 index 0000000000..a3154b6e73 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class OrderItemDeliveryTime : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(OrderItemDeliveryTime)); + + string IMigrationMetadata.Id + { + get { return "201807191020207_OrderItemDeliveryTime"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.cs b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.cs new file mode 100644 index 0000000000..39805ce878 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class OrderItemDeliveryTime : DbMigration + { + public override void Up() + { + AddColumn("dbo.OrderItem", "DeliveryTimeId", c => c.Int()); + AddColumn("dbo.OrderItem", "DisplayDeliveryTime", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.OrderItem", "DisplayDeliveryTime"); + DropColumn("dbo.OrderItem", "DeliveryTimeId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.resx b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.resx new file mode 100644 index 0000000000..545c1b0b4b --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807191020207_OrderItemDeliveryTime.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.Designer.cs new file mode 100644 index 0000000000..923d368911 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class DownloadVersions : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(DownloadVersions)); + + string IMigrationMetadata.Id + { + get { return "201807201157391_DownloadVersions"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.cs b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.cs new file mode 100644 index 0000000000..6210c475fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.cs @@ -0,0 +1,53 @@ +namespace SmartStore.Data.Migrations +{ + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Catalog; + using SmartStore.Data.Setup; + using SmartStore.Data.Utilities; + using System; + using System.Data.Entity.Migrations; + using System.Linq; + using System.Web.Hosting; + + public partial class DownloadVersions : DbMigration, IDataSeeder + { + public override void Up() + { + int entityId = 0; + + if (DataSettings.DatabaseIsInstalled() && HostingEnvironment.IsHosted) + { + var ctx = new SmartObjectContext(); + entityId = ctx.Set().Select(x => x.Id).FirstOrDefault(); + } + + AddColumn("dbo.Download", "EntityId", c => c.Int(nullable: false, defaultValue: entityId)); + AddColumn("dbo.Download", "EntityName", c => c.String(nullable: false, maxLength: 100)); + AddColumn("dbo.Download", "FileVersion", c => c.String(maxLength: 30)); + AddColumn("dbo.Download", "Changelog", c => c.String()); + CreateIndex("dbo.Download", new[] { "EntityId", "EntityName" }); + } + + public override void Down() + { + DropIndex("dbo.Download", new[] { "EntityId", "EntityName" }); + DropColumn("dbo.Download", "Changelog"); + DropColumn("dbo.Download", "FileVersion"); + DropColumn("dbo.Download", "EntityName"); + DropColumn("dbo.Download", "EntityId"); + } + + public bool RollbackOnFailure + { + get { return true; } + } + + public void Seed(SmartObjectContext context) + { + if (!HostingEnvironment.IsHosted) + return; + + DataMigrator.SetDownloadProductId(context); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.resx b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.resx new file mode 100644 index 0000000000..2cc63c0975 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807201157391_DownloadVersions.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs new file mode 100644 index 0000000000..0bcd55246f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class ProductPreviewPicture : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductPreviewPicture)); + + string IMigrationMetadata.Id + { + get { return "201807311708428_ProductPreviewPicture"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs new file mode 100644 index 0000000000..91c06bfb44 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ProductPreviewPicture : DbMigration + { + public override void Up() + { + AddColumn("dbo.Product", "HasPreviewPicture", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.Product", "HasPreviewPicture"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx new file mode 100644 index 0000000000..b90b2b768c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201807311708428_ProductPreviewPicture.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs new file mode 100644 index 0000000000..9002b8b1aa --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Merge4 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge4)); + + string IMigrationMetadata.Id + { + get { return "201808051119578_Merge4"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs new file mode 100644 index 0000000000..0777552123 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge4 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx new file mode 100644 index 0000000000..198ea117b6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051119578_Merge4.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs new file mode 100644 index 0000000000..c28cdd698c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class Merge4 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge4)); + + string IMigrationMetadata.Id + { + get { return "201808051818238_Merge4"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs new file mode 100644 index 0000000000..0777552123 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge4 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx new file mode 100644 index 0000000000..b90b2b768c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201808051818238_Merge4.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs new file mode 100644 index 0000000000..a8abf3ecf1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")] + public sealed partial class NewsletterSubscriptionLanguage : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(NewsletterSubscriptionLanguage)); + + string IMigrationMetadata.Id + { + get { return "201809171309522_NewsletterSubscriptionLanguage"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs new file mode 100644 index 0000000000..f1eccaf9e5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class NewsletterSubscriptionLanguage : DbMigration + { + public override void Up() + { + AddColumn("dbo.NewsLetterSubscription", "WorkingLanguageId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.NewsLetterSubscription", "WorkingLanguageId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx new file mode 100644 index 0000000000..b2b8d1b2db --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809171309522_NewsletterSubscriptionLanguage.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs index 706a0b839d..f5769b8124 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -1,14 +1,10 @@ -namespace SmartStore.Data.Migrations +namespace SmartStore.Data.Migrations { using System; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Linq; using Setup; - using SmartStore.Utilities; - using SmartStore.Core.Domain.Media; - using Core.Domain.Configuration; - using SmartStore.Core.Domain.Customers; public sealed class MigrationsConfiguration : DbMigrationsConfiguration { @@ -34,238 +30,477 @@ protected override void Seed(SmartObjectContext context) public void MigrateSettings(SmartObjectContext context) { - // Change MediaSettings.MaximumImageSize to 2048 - var name = TypeHelper.NameOf(y => y.MaximumImageSize, true); - var setting = context.Set().FirstOrDefault(x => x.Name == name); - if (setting != null && setting.Value.Convert() < 2048) - { - setting.Value = "2048"; - } - - // Change MediaSettings.AvatarPictureSize to 250 - name = TypeHelper.NameOf(y => y.AvatarPictureSize, true); - setting = context.Set().FirstOrDefault(x => x.Name == name); - if (setting != null && setting.Value.Convert() < 250) - { - setting.Value = "250"; - } - - // Change MediaSettings.AvatarMaximumSizeBytes to 512000 (500 KB) - name = TypeHelper.NameOf(y => y.AvatarMaximumSizeBytes, true); - setting = context.Set().FirstOrDefault(x => x.Name == name); - if (setting != null && setting.Value.Convert() < 512000) - { - setting.Value = "512000"; - } + } public void MigrateLocaleResources(LocaleResourcesBuilder builder) { - builder.AddOrUpdate("Admin.Orders.Shipment", "Shipment", "Lieferung"); - builder.AddOrUpdate("Admin.Order", "Order", "Auftrag"); - - builder.AddOrUpdate("Admin.Order.ViaShippingMethod", "via {0}", "via {0}"); - builder.AddOrUpdate("Admin.Order.WithPaymentMethod", "with {0}", "per {0}"); - builder.AddOrUpdate("Admin.Order.FromStore", "from {0}", "von {0}"); - - builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.MaxItemsToDisplayInCatalogMenu", - "Max items to display in catalog menu", - "Maximale Anzahl von Elementen im Katalogmen�", - "Defines the maximum number of top level items to be displayed in the main catalog menu. All menu items which are exceeding this limit will be placed in a new dropdown menu item.", - "Legt die maximale Anzahl von Menu-Eintr�gen der obersten Hierarchie fest, die im Katalogmen� angezeigt werden. Alle weiteren Menu-Eintr�ge werden innerhalb eines neuen Dropdownmenus ausgegeben."); - - builder.AddOrUpdate("CatalogMenu.MoreLink", "More", "Mehr"); - - builder.AddOrUpdate("Admin.CatalogSettings.Homepage", "Homepage", "Homepage"); - builder.AddOrUpdate("Admin.CatalogSettings.ProductDisplay", "Product display", "Produktdarstellung"); - builder.AddOrUpdate("Admin.CatalogSettings.Prices", "Prices", "Preise"); - builder.AddOrUpdate("Admin.CatalogSettings.CompareProducts", "Compare products", "Produktvergleich"); - - builder.AddOrUpdate("Footer.Service.Mobile", "Service", "Service, Versand & Zahlung"); - builder.AddOrUpdate("Footer.Company.Mobile", "Company", "Firma, Impressum & Datenschutz"); - - builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetSorting.LabelAsc", - "Displayed Name: A to Z", - "Angezeigter Name: A bis Z"); - - builder.AddOrUpdate("Admin.Catalog.Products.Copy.NumberOfCopies", - "Number of copies", - "Anzahl an Kopien", - "Defines the number of copies to be created.", - "Legt die Anzahl der anzulegenden Kopien fest."); - - builder.AddOrUpdate("Admin.Configuration.Languages.OfType", - "of type \"{0}\"", - "vom Typ \"{0}\""); - - builder.AddOrUpdate("Admin.Configuration.Languages.CheckAvailableLanguagesFailed", - "An error occurred while checking for other available languages.", - "Bei der Suche nach weiteren verf�gbaren Sprachen trat ein Fehler auf."); - - builder.AddOrUpdate("Admin.Configuration.Languages.NoAvailableLanguagesFound", - "There were no other available languages found for version {0}. On translate.smartstore.com you will find more details about available resources.", - "Es wurden keine weiteren verf�gbaren Sprachen f�r Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verf�gbaren Ressourcen."); - - builder.AddOrUpdate("Admin.Configuration.Languages.InstalledLanguages", - "Installed Languages", - "Installierte Sprachen"); - builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages", - "Available Languages", - "Verf�gbare Sprachen"); - - builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages.Note", - "Click Download to install a new language including all localized resources. On translate.smartstore.com you will find more details about available resources.", - "Klicken Sie auf Download, um eine neue Sprache mit allen lokalisierten Ressourcen zu installieren. Auf translate.smartstore.com finden Sie weitere Details zu verf�gbaren Ressourcen."); - - builder.AddOrUpdate("Common.Translated", - "Translated", - "�bersetzt"); - builder.AddOrUpdate("Admin.Configuration.Languages.TranslatedPercentage", - "{0}% translated", - "{0}% �bersetzt"); - builder.AddOrUpdate("Admin.Configuration.Languages.TranslatedPercentageAtLastImport", - "{0}% at the last import", - "{0}% beim letzten Import"); - - builder.AddOrUpdate("Admin.Configuration.Languages.NumberOfTranslatedResources", - "{0} of {1}", - "{0} von {1}"); - - builder.AddOrUpdate("Admin.Configuration.Languages.DownloadingResources", - "Loading ressources", - "Lade Ressourcen"); - builder.AddOrUpdate("Admin.Configuration.Languages.ImportResources", - "Import resources", - "Importiere Ressourcen"); - - builder.AddOrUpdate("Admin.Configuration.Languages.OnePublishedLanguageRequired", - "At least one published language is required.", - "Mindestens eine ver�ffentlichte Sprache ist erforderlich."); - - builder.AddOrUpdate("Admin.Configuration.Languages.Fields.AvailableLanguageSetId", - "Available Languages", - "Verf�gbare Sprachen", - "Specifies the available language whose localized resources are to be imported.", - "Legt die verf�gbare Sprache fest, deren lokalisierte Ressourcen importiert werden sollen."); - - builder.AddOrUpdate("Admin.Configuration.Languages.UploadFileOrSelectLanguage", - "Please upload an import file or select an available language whose resources are to be imported.", - "Bitte laden Sie eine Importdatei hoch oder w�hlen Sie eine verf�gbare Sprache, deren Ressourcen importiert werden sollen."); - - builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.ChargeOnlyHighestProductShippingSurcharge", - "Charge the highest shipping surcharge only", - "Nur den h�chsten Transportzuschlag berechnen", - "Specifies whether to charge only the highest additional shipping surcharge of products.", - "Bestimmt ob bei der Berechnung der Versandkosten nur der h�chste Transportzuschlag von Produkten ber�cksichtigt wird."); - - builder.AddOrUpdate("Order.OrderDetails") - .Value("en", "Order Details"); - - builder.AddOrUpdate("Admin.Configuration.Settings.Media.AutoGenerateAbsoluteUrls", - "Generate absolute URLs", - "Absolute URLs erzeugen", - "Generates absolute URLs for media files by prepending the current host name (e.g. http://myshop.com/media/image/1.jpg instead of /media/image/1.jpg). Has no effect if a CDN URL has been applied to the store.", - "Erzeugt absolute URLs f�r Mediendateien, indem der aktuelle Hostname vorangestellt wird (z.B. http://meinshop.de/media/image/1.jpg statt /media/image/1.jpg). Hat keine Auswirkung, wenn f�r den Store eine CDN-URL eingerichtet wurde."); - - builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchFieldsNote", - "The Name, SKU and Short Description fields can be searched in the standard search. Other fields require a search plugin such as the MegaSearch plugin from Premium Edition.", - "In der Standardsuche k�nnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. F�r weitere Felder ist ein Such-Plugin wie etwa das MegaSearch-Plugin aus der Premium Edition notwendig."); - - builder.AddOrUpdate("Admin.DataExchange.Import.FolderName", "Folder path", "Ordnerpfad"); - - builder.AddOrUpdate("Admin.MessageTemplate.Preview.From", "From", "Von"); - builder.AddOrUpdate("Admin.MessageTemplate.Preview.To", "To", "An"); - builder.AddOrUpdate("Admin.MessageTemplate.Preview.ReplyTo", "Reply To", "Antwort an"); - builder.AddOrUpdate("Admin.MessageTemplate.Preview.SendTestMail", "Test-E-mail to...", "Test E-Mail an..."); - builder.AddOrUpdate("Admin.MessageTemplate.Preview.TestMailSent", "E-mail has been sent.", "E-Mail gesendet."); - builder.AddOrUpdate("Admin.MessageTemplate.Preview.NoBody", - "The generated preview file seems to have expired. Please reload the page.", - "Die generierte Vorschaudatei scheint abgelaufen zu sein. Laden Sie die Seite bitte neu."); - - builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.Preview.SuccessfullySent", - "The email has been sent successfully.", - "Die E-Mail wurde erfolgreich versendet."); - - builder.AddOrUpdate("Admin.ContentManagement.MessageTemplates.SuccessfullyCopied", - "The message template has been copied successfully.", - "Die Nachrichtenvorlage wurde erfolgreich kopiert."); - - - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.ShoppingCartItem", "Shopping Cart", "Warenkorb"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.ShoppingCartType.ShoppingCart", "Shopping Cart", "Warenkorb"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.ShoppingCartType.Wishlist", "Wishlist", "Wunschliste"); - - builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NoBundleProducts", - "Do not export bundled products", - "Keine Produkt-Bundle exportieren", - "Specifies whether to export bundled products. If this option is activated, then the associated bundle items will be exported.", - "Legt fest, ob Produkt-Bundle exportiert werden sollen. Ist diese Option aktiviert, so werden die zum Bundle geh�renden Produkte (Bundle-Bestandteile) exportiert."); - - builder.AddOrUpdate("Admin.DataExchange.Export.Filter.ShoppingCartTypeId", - "Shopping cart type", - "Warenkorbtyp", - "Filter by shopping cart type.", - "Nach Warenkorbtyp filtern."); - - builder.AddOrUpdate("Common.CustomerId", "Customer ID", "Kunden ID"); - - builder.AddOrUpdate("Account.AccountActivation.InvalidEmailOrToken", - "Unknown email or token. Please register again.", - "Unbekannte E-Mail oder Token. Bitte f�hren Sie die Registrierung erneut durch."); - - builder.AddOrUpdate("Account.PasswordRecoveryConfirm.InvalidEmailOrToken", - "Unknown email or token. Please click \"Forgot password\" again, if you want to renew your password.", - "Unbekannte E-Mail oder Token. Klicken Sie bitte erneut \"Passwort vergessen\", falls Sie Ihr Passwort erneuern m�chten."); - - builder.Delete("Account.PasswordRecoveryConfirm.InvalidEmail"); - builder.Delete("Account.PasswordRecoveryConfirm.InvalidToken"); - - builder.AddOrUpdate("Admin.Common.Acl.SubjectTo", - "Restrict access", - "Zugriff einschr�nken", - "Determines whether this entity is subject to access restrictions (no = no restriction, yes = accessible only for selected customer groups)", - "Legt fest, ob dieser Datensatz Zugriffsbeschr�nkungen unterliegt (Nein = keine Beschr�nkung, Ja = zug�nglich nur f�r gew�hlte Kundengruppen)"); - - builder.AddOrUpdate("Admin.Common.Acl.AvailableFor", - "Customer roles", - "Kundengruppen", - "Select customer roles who can access the entity. For all inactive roles, this record is hidden.", - "W�hlen Sie Kundengruppen, die auf den Datensatz zugreifen k�nnen. Bei allen nicht aktivierten Gruppen wird dieser Datensatz ausgeblendet."); - + builder.AddOrUpdate("Admin.ReturnRequests.MaxRefundAmount", + "Maximum refund amount", + "Maximaler Erstattungsbetrag", + "The maximum amount that can be refunded for this return request.", + "Der maximale Betrag, der für diesen Rücksendewunsch erstattet werden kann."); + + builder.AddOrUpdate("Admin.Customers.Customers.Fields.Title", + "Title", + "Titel", + "Specifies the title.", + "Legt den Titel fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.FolderName.Validate", + "Please enter a valid, relative folder path for the export data. The path must be at least 3 characters long and not the application folder.", + "Bitte einen gültigen, relativen Ordnerpfad für die zu exportierenden Daten eingeben. Der Pfad muss mindestens 3 Zeichen lang und nicht der Anwendungsordner sein."); + + builder.AddOrUpdate("Admin.Catalog.Customers.CustomerSearchType", "Search in:", "Suche in:"); + + // Fix some FluentValidation german translations + builder.AddOrUpdate("Validation.LengthValidator") + .Value("de", "'{PropertyName}' muss zwischen {MinLength} und {MaxLength} Zeichen lang sein. Sie haben {TotalLength} Zeichen eingegeben."); + builder.AddOrUpdate("Validation.MinimumLengthValidator") + .Value("de", "'{PropertyName}' muss mind. {MinLength} Zeichen lang sein. Sie haben {TotalLength} Zeichen eingegeben."); + builder.AddOrUpdate("Validation.MaximumLengthValidator") + .Value("de", "'{PropertyName}' darf max. {MaxLength} Zeichen lang sein. Sie haben {TotalLength} Zeichen eingegeben."); + builder.AddOrUpdate("Validation.ExactLengthValidator") + .Value("de", "'{PropertyName}' muss genau {MaxLength} lang sein. Sie haben {TotalLength} Zeichen eingegeben."); + builder.AddOrUpdate("Validation.ExclusiveBetweenValidator") + .Value("de", "'{PropertyName}' muss größer als {From} und kleiner als {To} sein. Sie haben '{Value}' eingegeben."); + builder.AddOrUpdate("Validation.InclusiveBetweenValidator") + .Value("de", "'{PropertyName}' muss zwischen {From} and {To} liegen. Sie haben '{Value}' eingegeben."); + builder.AddOrUpdate("Validation.NotNullValidator") + .Value("de", "'{PropertyName}' ist erforderlich."); + builder.AddOrUpdate("Validation.NotEmptyValidator") + .Value("de", "'{PropertyName}' ist erforderlich."); + builder.AddOrUpdate("Validation.LessThanValidator") + .Value("de", "'{PropertyName}' muss kleiner sein als '{ComparisonValue}'."); + builder.AddOrUpdate("Validation.RegularExpressionValidator") + .Value("de", "'{PropertyName}' entspricht nicht dem erforderlichen Muster."); + builder.AddOrUpdate("Validation.ScalePrecisionValidator") + .Value("de", "'{PropertyName}' darf insgesamt nicht mehr als {expectedPrecision} Ziffern enthalten, unter Berücksichtigung von {expectedScale} Dezimalstellen. {digits} Ziffern und {actualScale} Dezimalstellen wurden gefunden."); + + // Some new resources for custom validators + builder.AddOrUpdate("Validation.CreditCardCvvNumberValidator", + "'{PropertyName}' is invalid.", + "'{PropertyName}' ist ungültig."); + + // Get rid of duplicate validator resource entries builder.Delete( - "Admin.Catalog.Categories.Fields.SubjectToAcl", - "Admin.Catalog.Categories.Fields.AclCustomerRoles", - "Admin.Catalog.Products.Fields.SubjectToAcl", - "Admin.Catalog.Products.Fields.AclCustomerRoles"); - - builder.AddOrUpdate("Admin.Common.ApplyFilter", "Apply filter", "Filter anwenden"); - builder.AddOrUpdate("Time.Milliseconds", "Milliseconds", "Millisekunden"); - builder.AddOrUpdate("Common.Pixel", "Pixel", "Pixel"); - builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ShowPlaceholder", "Show placeholder", "Zeige Platzhalter"); - builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.HidePlaceholder", "Hide placeholder", "Verberge Platzhalter"); - builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.UpdateExampleFileName", "Update example", "Aktualisiere Beispiel"); - - builder.AddOrUpdate("Admin.Configuration.Themes.AvailableDesktopThemes", "Installed themes", "Installierte Themes"); - - builder.AddOrUpdate("Admin.Catalog.Products.List.GoDirectlyToSku", "Find by SKU", "Nach SKU suchen"); - - builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.StoreLastIpAddress", - "Store IP address", - "IP-Adresse speichern", - "Specifies whether to store the IP address in the customer data set.", - "Legt fest, ob die IP-Adresse im Kundendatensatz gespeichert werden soll."); - - builder.AddOrUpdate("Admin.Orders.Info", "General", "Allgemein"); - builder.AddOrUpdate("Admin.Orders.BillingAndShipment", "Billing & Shipping", "Rechnung & Versand"); - builder.AddOrUpdate("Admin.Orders.Fields.ShippingAddress.ViewOnGoogleMaps", "View on Google Maps", "Auf Google Maps ansehen"); - - builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.SocialSettings.InstagramLink", - "Instagram Link", - "Instagram Link", - "Leave this field empty if the Instagram link should not be shown", - "Lassen Sie dieses Feld leer, wenn der Instagram Link nicht angezeigt werden soll"); - - builder.AddOrUpdate("Common.License", "License", "Lizenz"); - } - } + "Admin.Catalog.Products.Fields.Name.Required", + "Admin.Catalog.Categories.Fields.Name.Required", + "Admin.Catalog.Manufacturers.Fields.Name.Required", + "Admin.Validation.RequiredField", + "Admin.Catalog.Attributes.ProductAttributes.Fields.Name.Required", + "Admin.Catalog.ProductReviews.Fields.Title.Required", + "Admin.Catalog.ProductReviews.Fields.ReviewText.Required", + "Admin.Catalog.ProductTags.Fields.Name.Required", + "Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Name.Required", + "Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.Quantity.GreaterOrEqualToOne", + "Admin.Catalog.Attributes.SpecificationAttributes.Options.Fields.Name.Required", + "Admin.Catalog.Attributes.SpecificationAttributes.Fields.Name.Required", + "Admin.ContentManagement.Blog.BlogPosts.Fields.Title.Required", + "Admin.ContentManagement.Blog.BlogPosts.Fields.Body.Required", + "Admin.Common.GenericAttributes.Fields.Name.Required", + "Admin.Customers.CustomerRoles.Fields.Name.Required", + "Admin.Configuration.Countries.Fields.Name.Required", + "Admin.Configuration.Countries.Fields.TwoLetterIsoCode.Required", + "Admin.Configuration.Countries.Fields.TwoLetterIsoCode.Length", + "Admin.Configuration.Countries.Fields.ThreeLetterIsoCode.Required", + "Admin.Configuration.Countries.Fields.ThreeLetterIsoCode.Length", + "Admin.Configuration.Measures.Dimensions.Fields.Name.Required", + "Admin.Configuration.Measures.Dimensions.Fields.SystemKeyword.Required", + "Admin.Configuration.Measures.Weights.Fields.Name.Required", + "Admin.Configuration.Measures.Weights.Fields.SystemKeyword.Required", + "Admin.Configuration.Countries.States.Fields.Name.Required", + "Admin.Configuration.DeliveryTimes.Fields.Name.Required", + "Admin.Configuration.DeliveryTimes.Fields.ColorHexValue.Required", + "Admin.Configuration.DeliveryTimes.Fields.ColorHexValue.Range", + "Admin.Configuration.DeliveryTimes.Fields.Name.Range", + "Admin.Configuration.Currencies.Fields.Name.Required", + "Admin.Configuration.Currencies.Fields.Name.Range", + "Admin.Configuration.Currencies.Fields.CurrencyCode.Required", + "Admin.Configuration.Currencies.Fields.CurrencyCode.Range", + "Admin.Configuration.Currencies.Fields.Rate.Range", + "Admin.Configuration.Currencies.Fields.CustomFormatting.Validation", + "Admin.Promotions.Discounts.Fields.Name.Required", + "Admin.ContentManagement.Forums.ForumGroup.Fields.Name.Required", + "Admin.ContentManagement.Forums.Forum.Fields.Name.Required", + "Admin.ContentManagement.Forums.Forum.Fields.ForumGroupId.Required", + "Admin.Configuration.Languages.Resources.Fields.Name.Required", + "Admin.Configuration.Languages.Resources.Fields.Value.Required", + "Admin.Configuration.Languages.Fields.Name.Required", + "Admin.Configuration.Languages.Fields.UniqueSeoCode.Required", + "Admin.Configuration.Languages.Fields.UniqueSeoCode.Length", + "Admin.Promotions.Campaigns.Fields.Name.Required", + "Admin.Promotions.Campaigns.Fields.Subject.Required", + "Admin.Promotions.Campaigns.Fields.Body.Required", + "Admin.ContentManagement.MessageTemplates.Fields.Subject.Required", + "Admin.ContentManagement.MessageTemplates.Fields.Body.Required", + "Admin.Promotions.NewsLetterSubscriptions.Fields.Email.Required", + "Admin.System.QueuedEmails.Fields.Priority.Required", + "Admin.System.QueuedEmails.Fields.From.Required", + "Admin.System.QueuedEmails.Fields.To.Required", + "Admin.System.QueuedEmails.Fields.SentTries.Required", + "Admin.System.QueuedEmails.Fields.Priority.Range", + "Admin.System.QueuedEmails.Fields.SentTries.Range", + "Admin.ContentManagement.News.NewsItems.Fields.Title.Required", + "Admin.ContentManagement.News.NewsItems.Fields.Short.Required", + "Admin.ContentManagement.News.NewsItems.Fields.Full.Required", + "Admin.Catalog.Attributes.CheckoutAttributes.Fields.Name.Required", + "Admin.Catalog.Attributes.CheckoutAttributes.Values.Fields.Name.Required", + "Admin.Configuration.Plugins.Fields.FriendlyName.Required", + "Admin.ContentManagement.Polls.Answers.Fields.Name.Required", + "Admin.ContentManagement.Polls.Fields.Name.Required", + "Admin.Configuration.Shipping.Methods.Fields.Name.Required", + "Admin.Configuration.Stores.Fields.Name.Required", + "Admin.Configuration.Stores.Fields.Url.Required", + "Admin.Configuration.Settings.AllSettings.Fields.Name.Required", + "Admin.System.ScheduleTasks.Name.Required", + "Admin.Configuration.Tax.Categories.Fields.Name.Required", + "Admin.ContentManagement.Topics.Fields.SystemName.Required", + "Admin.Address.Fields.FirstName.Required", + "Admin.Address.Fields.LastName.Required", + "Admin.Address.Fields.Email.Required", + "Admin.Address.Fields.Company.Required", + "Admin.Address.Fields.City.Required", + "Admin.Address.Fields.Address1.Required", + "Admin.Address.Fields.Address2.Required", + "Admin.Address.Fields.ZipPostalCode.Required", + "Admin.Address.Fields.PhoneNumber.Required", + "Admin.Address.Fields.FaxNumber.Required", + "Admin.Address.Fields.EmailMatch.Required", + "Admin.Customers.Customers.Fields.FirstName.Required", + "Admin.Customers.Customers.Fields.LastName.Required", + "Admin.Customers.Customers.Fields.Company.Required", + "Admin.Customers.Customers.Fields.StreetAddress.Required", + "Admin.Customers.Customers.Fields.StreetAddress2.Required", + "Admin.Customers.Customers.Fields.ZipPostalCode.Required", + "Admin.Customers.Customers.Fields.City.Required", + "Admin.Customers.Customers.Fields.Phone.Required", + "Admin.Customers.Customers.Fields.Fax.Required", + "Admin.Validation.Name", + "Admin.Validation.EmailAddress", + "Admin.Validation.Url", + "Admin.Validation.UsernamePassword", + "Admin.DataExchange.Export.FileNamePattern.Validate", + "Admin.DataExchange.Export.Partition.Validate", + "Admin.Common.WrongEmail", + "Payment.CardCode.Wrong" + ); + + // Get rid of duplicate CreatedOn resources also + builder.Delete( + "Admin.Affiliates.Orders.CreatedOn", + "Admin.ContentManagement.Blog.Comments.Fields.CreatedOn", + "Admin.ContentManagement.Blog.BlogPosts.Fields.CreatedOn", + "Admin.ContentManagement.Blog.BlogPosts.Fields.CreatedOn", + "Admin.Catalog.ProductReviews.Fields.CreatedOn", + "Admin.Customers.Customers.Fields.CreatedOn", + "Admin.Customers.Customers.Orders.CreatedOn", + "Admin.Customers.Customers.ActivityLog.CreatedOn", + "Admin.Orders.Fields.CreatedOn", + "Admin.Customers.Customers.Fields.CreatedOn", + "Admin.Promotions.NewsLetterSubscriptions.Fields.CreatedOn", + "Admin.Configuration.Currencies.Fields.CreatedOn", + "Admin.Promotions.Discounts.History.CreatedOn", + "Admin.ContentManagement.Forums.ForumGroup.Fields.CreatedOn", + "Admin.ContentManagement.Forums.Forum.Fields.CreatedOn", + "Admin.Configuration.ActivityLog.ActivityLog.Fields.CreatedOn", + "Admin.System.Log.Fields.CreatedOn", + "Admin.Promotions.Campaigns.Fields.CreatedOn", + "Admin.Promotions.NewsLetterSubscriptions.Fields.CreatedOn", + "Admin.System.QueuedEmails.Fields.CreatedOn", + "Admin.ContentManagement.News.Comments.Fields.CreatedOn", + "Admin.ContentManagement.News.NewsItems.Fields.CreatedOn", + "Admin.GiftCards.Fields.CreatedOn", + "Admin.GiftCards.History.CreatedOn", + "Admin.Orders.Fields.CreatedOn", + "Admin.Orders.OrderNotes.Fields.CreatedOn", + "Admin.RecurringPayments.History.CreatedOn", + "Admin.ReturnRequests.Fields.CreatedOn" + ); + + // duplicate validator resource entries in frontend + builder.Delete( + "Blog.Comments.CommentText.Required", + "Forum.TextCannotBeEmpty", + "Forum.TopicSubjectCannotBeEmpty", + "Forum.TextCannotBeEmpty", + "Account.Fields.Email.Required", + "Products.AskQuestion.Question.Required", + "Account.Fields.FullName.Required", + "Products.EmailAFriend.FriendEmail.Required", + "Products.EmailAFriend.YourEmailAddress.Required", + "Reviews.Fields.Title.Required", + "Reviews.Fields.Title.MaxLengthValidation", + "Reviews.Fields.ReviewText.Required", + "Address.Fields.FirstName.Required", + "Address.Fields.LastName.Required", + "Address.Fields.Email.Required", + "Account.Fields.Company.Required", + "Account.Fields.StreetAddress.Required", + "Account.Fields.StreetAddress2.Required", + "Account.Fields.ZipPostalCode.Required", + "Account.Fields.City.Required", + "Account.Fields.Phone.Required", + "Account.Fields.Fax.Required", + "Admin.Address.Fields.EmailMatch.Required", + "ContactUs.Email.Required", + "ContactUs.Enquiry.Required", + "ContactUs.FullName.Required", + "Account.ChangePassword.Fields.OldPassword.Required", + "Account.ChangePassword.Fields.NewPassword.Required", + "Account.ChangePassword.Fields.NewPassword.LengthValidation", + "Account.ChangePassword.Fields.ConfirmNewPassword.Required", + "Account.ChangePassword.Fields.NewPassword.LengthValidation", + "Account.Fields.Email.Required", + "Account.Fields.FirstName.Required", + "Account.Fields.LastName.Required", + "Account.Fields.Company.Required", + "Account.Fields.StreetAddress.Required", + "Account.Fields.StreetAddress2.Required", + "Account.Fields.ZipPostalCode.Required", + "Account.Fields.City.Required", + "Account.Fields.Phone.Required", + "Account.Fields.Fax.Required", + "Account.Fields.Password.Required", + "Account.Fields.Vat.Required", + "Account.PasswordRecovery.NewPassword.Required", + "Account.PasswordRecovery.NewPassword.LengthValidation", + "Account.PasswordRecovery.ConfirmNewPassword.Required", + "Account.PasswordRecovery.Email.Required", + "News.Comments.CommentTitle.Required", + "News.Comments.CommentTitle.MaxLengthValidation", + "News.Comments.CommentText.Required", + "PrivateMessages.SubjectCannotBeEmpty", + "PrivateMessages.MessageCannotBeEmpty", + "Wishlist.EmailAFriend.FriendEmail.Required", + "Wishlist.EmailAFriend.YourEmailAddress.Required" + ); + + // remove duplicate resources for display order + builder.Delete( + "Admin.Catalog.Categories.Fields.DisplayOrder", + "Admin.Catalog.Categories.Products.Fields.DisplayOrder", + "Admin.Catalog.Manufacturers.Fields.DisplayOrder", + "Admin.Catalog.Manufacturers.Products.Fields.DisplayOrder", + "Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.DisplayOrder", + "Admin.Catalog.Products.BundleItems.Fields.DisplayOrder", + "Admin.Catalog.Products.Fields.HomePageDisplayOrder", + "Admin.Catalog.Products.SpecificationAttributes.Fields.DisplayOrder", + "Admin.Catalog.Products.Pictures.Fields.DisplayOrder", + "Admin.Catalog.Products.Categories.Fields.DisplayOrder", + "Admin.Catalog.Products.Manufacturers.Fields.DisplayOrder", + "Admin.Catalog.Products.RelatedProducts.Fields.DisplayOrder", + "Admin.Catalog.Products.AssociatedProducts.Fields.DisplayOrder", + "Admin.Catalog.Products.BundleItems.Fields.DisplayOrder", + "Admin.Catalog.Products.ProductVariantAttributes.Attributes.Fields.DisplayOrder", + "Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.Fields.DisplayOrder", + "Admin.Catalog.Products.SpecificationAttributes.Fields.DisplayOrder", + "Admin.Catalog.Attributes.SpecificationAttributes.Fields.DisplayOrder", + "Admin.Catalog.Attributes.SpecificationAttributes.Options.Fields.DisplayOrder", + "Admin.Catalog.Categories.Fields.DisplayOrder", + "Admin.Catalog.Manufacturers.Fields.DisplayOrder", + "Admin.Configuration.Countries.Fields.DisplayOrder", + "Admin.Configuration.Currencies.Fields.DisplayOrder", + "Admin.Configuration.DeliveryTimes.Fields.DisplayOrder", + "Admin.Configuration.Measures.Dimensions.Fields.DisplayOrder", + "Admin.Configuration.Measures.Weights.Fields.DisplayOrder", + "Admin.Configuration.Countries.States.Fields.DisplayOrder", + "Admin.ContentManagement.Forums.ForumGroup.Fields.DisplayOrder", + "Admin.ContentManagement.Forums.Forum.Fields.DisplayOrder", + "Admin.Configuration.Languages.Fields.DisplayOrder", + "Admin.Catalog.Attributes.CheckoutAttributes.Fields.DisplayOrder", + "Admin.Catalog.Attributes.CheckoutAttributes.Values.Fields.DisplayOrder", + "Admin.Configuration.Plugins.Fields.DisplayOrder", + "Admin.ContentManagement.Polls.Answers.Fields.DisplayOrder", + "Admin.ContentManagement.Polls.Fields.DisplayOrder", + "Admin.Configuration.Shipping.Methods.Fields.DisplayOrder", + "Admin.Configuration.Stores.Fields.DisplayOrder", + "Admin.Configuration.Tax.Categories.Fields.DisplayOrder" + ); + + builder.AddOrUpdate("Common.DisplayOrder.Hint", + "Specifies display order. 1 represents the top of the list.", + "Legt die Anzeige-Priorität fest. 1 steht bspw. für das erste Element in der Liste."); + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.UseInvisibleReCaptcha", + "Use invisible reCAPTCHA", + "Unsichtbaren reCAPTCHA verwenden", + "Does not require the user to click on a checkbox, instead it is invoked directly when the user submits a form. By default only the most suspicious traffic will be prompted to solve a captcha.", + "Der Benutzer muss nicht auf ein Kontrollkästchen klicken, sondern die Validierung erfolgt direkt beim Absenden eines Formulars. Nur bei 'verdächtigem' Traffic wird der Benutzer aufgefordert, ein Captcha zu lösen."); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.Fields.ShortTitle", + "Short title", + "Kurztitel", + "Optional. Used as link text. If empty, 'Title' sets the link text.", + "Optional. Wird u.A. als Linktext verwendet. Wenn leer, stellt 'Titel' den Linktext."); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.Fields.Intro", + "Intro", + "Intro", + "Optional. Short introduction / teaser.", + "Optional. Einleitung / Teaser."); + + builder.AddOrUpdate("Common.Download.Versions", "Versions", "Versionen"); + builder.AddOrUpdate("Common.Download.Version", "Version", "Version"); + builder.AddOrUpdate("Common.Download.Delete", "Delete download", "Download löschen"); + builder.AddOrUpdate("Common.Downloads", "Downloads", "Downloads"); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.NewVersionDownloadId", + "New download version", + "Neue Version des Downloads", + "Upload a new version of the download file here.", + "Laden Sie hier eine neue Version der Download-Datei hoch."); + + builder.AddOrUpdate("Admin.Catalog.Products.Download.VersionDelete", "Delete this file version.", "Diese Dateiversion löschen."); + builder.AddOrUpdate("Admin.Catalog.Products.Download.AddChangelog", "Edit changelog", "Änderungshistorie bearbeiten"); + builder.AddOrUpdate("Customer.Downloads.NoChangelogAvailable", "No changelog available.", "Keine Änderungshistorie verfügbar."); + + builder.AddOrUpdate("Admin.Catalog.Products.Download.SemanticVersion.NotValid", + "The specified version information is not valid. Please enter the version number in the correct format (e.g.: 1.0.0.0, 2.0 or 3.1.5).", + "Die angegebenen Versionsinformationen sind nicht gültig. Bitte geben Sie die Versionsnummer in korrektem Format an (z.B.: 1.0.0.0, 2.0 oder 3.1.5)."); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.HasPreviewPicture", + "Exclude first image from gallery", + "Erstes Bild aus Gallerie ausschließen", + "Activate this option if the first image should be displayed as a preview in product lists but not in the product detail gallery.", + "Aktivieren Sie diese Option, wenn das erste Bild als Vorschau in Produktlisten, nicht aber in der Produktdetail-Gallerie angezeigt werden soll."); + + builder.AddOrUpdate("Products.Free", "Free", "Kostenlos"); + + builder.AddOrUpdate("Admin.Catalog.Products.Fields.ProductTags.Hint", + "Product tags are keywords that this product can also be identified by. Enter a list of the tags to be associated with this product. The more products associated with a particular tag, the larger it will show on the tag cloud.", + "Eine Liste von Schlüsselwörtern, die das Produkt taxonomisch charakterisieren. Je mehr Produkte einem Schlüsselwort (Tag) zugeordnet sind, desto mehr visuelles Gewicht erhält das Tag."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Initial", "Position", "Position"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.Relevance", "Relevance", "Beste Ergebnisse"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectAsc", "Title: A to Z", "Titel: A bis Z"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.SubjectDesc", "Title: Z to A", "Titel: Z bis A"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.UserNameAsc", "User name: A to Z", "Benutzername: A bis Z"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.UserNameDesc", "User name: Z to A", "Benutzername: Z bis A"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnAsc", "Created on: Oldest first", "Erstellt am: ältere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.CreatedOnDesc", "Created on: Newest first", "Erstellt am: neuere zuerst"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsAsc", "Post number: ascending", "Anzahl Beiträge: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beiträge: absteigend"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastVisit", "Since last visit", "Seit dem letzten Besuch"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.Yesterday", "Yesterday", "Gestern"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastWeek", "Last week", "In der letzten Woche"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastTwoWeeks", "Last 2 weeks", "In den letzten 2 Wochen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastMonth", "Last month", "Im letzten Monat"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastThreeMonths", "Last 3 months", "In den letzten 3 Monaten"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastSixMonths", "Last 6 months", "In den letzten 6 Monaten"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter.LastYear", "Last year", "Im letzten Jahr"); + + builder.AddOrUpdate("Search.Facet.Forum", "Forum", "Forum"); + builder.AddOrUpdate("Search.Facet.Customer", "User name", "Benutzername"); + builder.AddOrUpdate("Search.Facet.Date", "Period", "Zeitraum"); + builder.AddOrUpdate("Search.Facet.Date.Newer", "and newer", "und neuer"); + builder.AddOrUpdate("Search.Facet.Date.Older", "and older", "und älter"); + + builder.AddOrUpdate("Forum.PostText", "Post text", "Beitragstext"); + builder.AddOrUpdate("Forum.Sticky", "Sticky topic", "Festes Thema"); + + builder.AddOrUpdate("Search.HitsFor", "{0} hits for {1}", "{0} Treffer für {1}"); + builder.AddOrUpdate("Search.NoMoreHitsFound", "There were no more hits found.", "Es wurden keine weiteren Treffer gefunden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.WildcardSearchNote", + "The wildcard mode can slow down the search for a large number of objects.", + "Der Wildcard-Modus kann bei einer großen Anzahl an Objekten die Suche verlangsamen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.SearchMode", + "Search mode", + "Suchmodus", + "Specifies the search mode. Please keep in mind that the search mode can - depending on number of objects - strongly affect search performance. 'Is equal to' is the fastest, 'Contains' the slowest.", + "Legt den Suchmodus fest. Bitte beachten Sie, dass der Suchmodus die Geschwindigkeit der Suche (abhängig von der Objektanzahl) beeinflusst. 'Ist gleich' ist am schnellsten, 'Beinhaltet' am langsamsten."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.Forum.SearchFields", + "Search fields", + "Suchfelder", + "Specifies additional search fields. The topic title is always searched.", + "Legt zusätzlich zu durchsuchende Felder fest. Der Thementitel wird grundsätzlich immer durchsucht."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.DefaultSortOrder", + "Default sort order", + "Standardsortierreihenfolge", + "Specifies the default sort order in search results.", + "Legt die Standardsortierreihenfolge in den Suchergebnissen fest."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Search.InstantSearchNumberOfHits", + "Number of hits", + "Anzahl der Treffer", + "Specifies the number of hits displayed in instant search.", + "Legt die Anzahl der angezeigten Suchtreffer in der Instantsuche fest."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowSorting", + "Allow sorting", + "Sortierung zulassen", + "Specifies whether forum posts can be sorted.", + "Legt fest, ob Forenbeiträge sortiert werden können."); + + builder.AddOrUpdate("Admin.Common.DefaultPageSizeOptions", + "Page size options", + "Auswahlmöglichkeiten für Seitengröße", + "Comma-separated page size options that a customer can select in lists.", + "Kommagetrennte Liste mit Optionen für Seitengröße, die ein Kunde in Listen wählen kann."); + + builder.AddOrUpdate("Admin.Common.AllowCustomersToSelectPageSize", + "Allow customers to select page size", + "Kunde kann Listengröße ändern", + "Whether customers are allowed to select the page size from a predefined list of options.", + "Kunden können die Listengröße mit Hilfe einer vorgegebenen Optionsliste ändern."); + + + builder.Delete( + "Admin.Configuration.Settings.Search.DefaultSortOrderMode", + "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", + "Forum.Search.LimitResultsToPrevious.AllResults", + "Forum.Search.LimitResultsToPrevious.1day", + "Forum.Search.LimitResultsToPrevious.7days", + "Forum.Search.LimitResultsToPrevious.2weeks", + "Forum.Search.LimitResultsToPrevious.1month", + "Forum.Search.LimitResultsToPrevious.3months", + "Forum.Search.LimitResultsToPrevious.6months", + "Forum.Search.LimitResultsToPrevious.1year", + "Forum.Search.SearchInForum.All", + "Forum.Search.SearchWithin.All", + "Forum.Search.SearchWithin.TopicTitlesOnly", + "Forum.Search.SearchWithin.PostTextOnly", + "Forum.SearchTermMinimumLengthIsNCharacters", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.All", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.PostTextOnly", + "Enums.SmartStore.Core.Domain.Forums.ForumSearchType.TopicTitlesOnly", + "Forum.AdvancedSearch", + "Forum.SearchButton", + "Forum.PageTitle.Search"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PriceDisplayStyle", + "Price display style", + "Preisdarstellung", + "Specifies the form in which prices are displayed in product lists and on the product detail page.", + "Bestimmt die Darstellungform von Preisen in Produktlisten und auf der Produktdetailseite."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.DisplayTextForZeroPrices", + "Display text when prices are 0,00", + "Zeige Text wenn Preise 0,00 sind", + "Specifies whether to display a textual resource (free) instead of the value 0.00.", + "Bestimmt, ob statt dem Wert 0,00 eine textuelle Resource (kostenlos) angezeigt werden soll."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.Default", "Default", "Standard"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeAll", "In bagdes", "Markiert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayStyle.BadgeFreeProductsOnly", "Badge free products only", "Nur kostenlose Produkte markieren"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Filter.WorkingLanguageId", + "Language", + "Sprache", + "Filter by language", + "Nach Sprache filtern"); + + + builder.AddOrUpdate("Admin.Configuration.Settings.GeneralCommon.CaptchaShowOnForumPage", + "Show on forum pages", + "Auf Forenseiten anzeigen", + "Specifies whether to display a CAPTCHA on forum pages when creating or replying to a topic.", + "Legt fest, ob ein CAPTCHA auf Forenseiten angezeigt werden soll, wenn ein Thema erstellt oder darauf geantwortet wird."); + } + } } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesBuilder.cs b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesBuilder.cs index 6305f33373..2f90e53a5b 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesBuilder.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesBuilder.cs @@ -5,7 +5,6 @@ namespace SmartStore.Data.Setup { - internal class LocaleResourceEntry { public string Key { get; set; } diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs index c0b2227841..85718543d3 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/LocaleResourcesMigrator.cs @@ -10,7 +10,6 @@ namespace SmartStore.Data.Setup { - internal class LocaleResourcesMigrator { private readonly SmartObjectContext _ctx; @@ -33,7 +32,7 @@ public void Migrate(IEnumerable entries, bool updateTouched if (!entries.Any() || !_languages.Any()) return; - using (var scope = new DbContextScope(_ctx, autoDetectChanges: false)) + using (var scope = new DbContextScope(_ctx, autoDetectChanges: false, hooksEnabled: false)) { var langMap = _languages.ToDictionarySafe(x => x.UniqueSeoCode.EmptyNull().ToLower()); @@ -53,8 +52,7 @@ public void Migrate(IEnumerable entries, bool updateTouched var validEntries = entries.Where(x => x.Lang == null || langMap[x.Lang.ToLower()].Id == lang.Value.Id); foreach (var entry in validEntries) { - bool isLocal; - var db = GetResource(entry.Key, lang.Value.Id, toAdd, out isLocal); + var db = GetResource(entry.Key, lang.Value.Id, toAdd, out bool isLocal); if (db == null && entry.Value.HasValue() && !entry.UpdateOnly) { diff --git a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs index c3d3ca0eef..b3d707666e 100644 --- a/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs +++ b/src/Libraries/SmartStore.Data/Setup/Builder/SettingsMigrator.cs @@ -61,7 +61,8 @@ public void Migrate(IEnumerable entries) if (HasSettings(entry.Key, false)) continue; // skip existing (we don't perform updates) - _settings.Add(new Setting { + _settings.Add(new Setting + { Name = entry.Key, Value = entry.Value, StoreId = 0 diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index 1991c7d170..e61e2c513b 100644 --- a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs +++ b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs @@ -4,11 +4,11 @@ using System.IO; using System.Linq; using System.Text; +using SmartStore.Core; using SmartStore.Core.Configuration; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; @@ -30,12 +30,11 @@ using SmartStore.Core.Domain.Tax; using SmartStore.Core.Domain.Themes; using SmartStore.Core.Domain.Topics; -using SmartStore.Data; using SmartStore.Utilities; namespace SmartStore.Data.Setup { - public abstract class InvariantSeedData + public abstract class InvariantSeedData { private SmartObjectContext _ctx; private string _sampleImagesPath; @@ -47,10 +46,10 @@ protected InvariantSeedData() public void Initialize(SmartObjectContext context) { - this._ctx = context; + _ctx = context; - this._sampleImagesPath = CommonHelper.MapPath("~/content/samples/"); - this._sampleDownloadsPath = CommonHelper.MapPath("~/content/samples/"); + _sampleImagesPath = CommonHelper.MapPath("~/App_Data/Samples/"); + _sampleDownloadsPath = CommonHelper.MapPath("~/App_Data/Samples/"); } #region Mandatory data creators @@ -60,10 +59,6 @@ public IList Pictures() var entities = new List { CreatePicture(File.ReadAllBytes(_sampleImagesPath + "company_logo.png"), "image/png", GetSeName("company-logo")), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "clouds.png"), "image/png", GetSeName("slider-bg")), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "iphone.png"), "image/png", GetSeName("slide-1")), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "music.png"), "image/png", GetSeName("slide-2")), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "packshot-net.png"), "image/png", GetSeName("slide-3")), CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_allstar_charcoal.jpg"), "image/jpeg", "all-star-charcoal"), CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_allstar_maroon.jpg"), "image/jpeg", "all-star-maroon"), @@ -71,11 +66,10 @@ public IList Pictures() CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_allstar_purple.jpg"), "image/jpeg", "all-star-purple"), CreatePicture(File.ReadAllBytes(_sampleImagesPath + "product_allstar_white.jpg"), "image/jpeg", "all-star-white"), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_havana.png"), "image/jpeg", "wayfarer_havana"), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_havana_black.png"), "image/jpeg", "wayfarer_havana_black"), - CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_rayban-black.png"), "image/jpeg", "wayfarer_rayban_black"), - - }; + CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_havana.png"), "image/png", "wayfarer_havana"), + CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_havana_black.png"), "image/png", "wayfarer_havana_black"), + CreatePicture(File.ReadAllBytes(_sampleImagesPath + "wayfarer_rayban-black.png"), "image/png", "wayfarer_rayban_black") + }; this.Alter(entities); return entities; @@ -3895,7 +3889,7 @@ public IList Topics() SystemName = "ContactUs", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "", + Title = "Contact us", Body = "

Put your contact information here. You can edit this in the admin site.

" }, new Topic @@ -3927,6 +3921,7 @@ public IList Topics() SystemName = "PrivacyInfo", IncludeInSitemap = false, IsPasswordProtected = false, + ShortTitle = "Privacy", Title = "Privacy policy", Body = "

" }, @@ -3939,7 +3934,6 @@ public IList Topics() Body = "

Put your shipping & returns information here. You can edit this in the admin site.

" }, - //codehint: sm-add begin new Topic { SystemName = "Imprint", @@ -4018,9 +4012,6 @@ public IList Settings() BaseDimensionId = _ctx.Set().Where(m => m.SystemKeyword == "inch").Single().Id, BaseWeightId = _ctx.Set().Where(m => m.SystemKeyword == "lb").Single().Id, }, - new MessageTemplatesSettings() - { - }, new ShoppingCartSettings() { }, @@ -7525,7 +7516,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_blue-gray-classic-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion blue-gray-classic-black @@ -7544,7 +7535,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_gray-course-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-black @@ -7563,7 +7554,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_gray-course-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-havana @@ -7582,7 +7573,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion green-classic-havana-black @@ -7603,7 +7594,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion green-classic-havana-black @@ -7622,7 +7613,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-blue-gray-classic-black-1").Id.ToString() }); #endregion green-classic-rayban-black @@ -7642,7 +7633,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = true, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-havana-black @@ -7661,7 +7652,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-gray-course-black").Id.ToString() }); #endregion gray-course-rayban-black @@ -7680,7 +7671,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion green-classic-rayban-black @@ -7699,7 +7690,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-green-classic-havana-black").Id.ToString() }); #endregion gray-course-rayban-black @@ -7719,7 +7710,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-havana-black @@ -7738,7 +7729,7 @@ public IList ProductVariantAttributeCombinat AllowOutOfStockOrders = true, IsActive = false, //Price = 299M, - AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename.EndsWith("_green-classic-havana-black")).Id.ToString() + AssignedPictureIds = picturesWayfarer.First(x => x.SeoFilename == "wayfarer-brown-course-havana").Id.ToString() }); #endregion brown-course-rayban-black @@ -10153,28 +10144,21 @@ public IList Products() { #region definitions - // Pictures - var sampleImagesPath = this._sampleImagesPath; - - // Downloads - var sampleDownloadsPath = this._sampleDownloadsPath; - - // Templates var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); - var firstDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 0); - + var secondDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 1); + var thirdDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 2); var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); #endregion definitions #region category golf - var categoryGolf = this._ctx.Set().First(c => c.Alias == "Golf"); + var categoryGolf = _ctx.Set().First(c => c.Alias == "Golf"); #region product Titleist SM6 Tour Chrome - var productTitleistSM6TourChrome = new Product() + var productTitleistSM6TourChrome = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10197,18 +10181,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productTitleistSM6TourChrome, "product_titleist_sm6_tour_chrome.jpg"); - productTitleistSM6TourChrome.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist_sm6_tour_chrome.jpg"), "image/png", GetSeName(productTitleistSM6TourChrome.Name)), - DisplayOrder = 1, - }); + productTitleistSM6TourChrome.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer() + productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10218,7 +10198,7 @@ public IList Products() #region product Titleist Pro V1x - var productTitleistProV1x = new Product() + var productTitleistProV1x = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10240,18 +10220,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTitleistProV1x.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productTitleistProV1x, "product_titleist-pro-v1x.jpg"); - productTitleistProV1x.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_titleist-pro-v1x.jpg"), "image/png", GetSeName(productTitleistProV1x.Name)), - DisplayOrder = 1, - }); + productTitleistProV1x.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer() + productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10261,7 +10237,7 @@ public IList Products() #region product Supreme Golfball - var productSupremeGolfball = new Product() + var productSupremeGolfball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10283,24 +10259,15 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productSupremeGolfball.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); - - productSupremeGolfball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_1.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_1.jpg", "golfball-1"); + AddProductPicture(productSupremeGolfball, "product_supremeGolfball_2.jpg", "golfball-2"); - productSupremeGolfball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_supremeGolfball_2.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), - DisplayOrder = 1, - }); + productSupremeGolfball.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer() + productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10310,7 +10277,7 @@ public IList Products() #region product GBB Epic Sub Zero Driver - var productGBBEpicSubZeroDriver = new Product() + var productGBBEpicSubZeroDriver = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10332,18 +10299,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory() { Category = categoryGolf, DisplayOrder = 1 }); + AddProductPicture(productGBBEpicSubZeroDriver, "product_gbb-epic-sub-zero-driver.jpg"); - productGBBEpicSubZeroDriver.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gbb-epic-sub-zero-driver.jpg"), "image/png", GetSeName(productGBBEpicSubZeroDriver.Name)), - DisplayOrder = 1, - }); + productGBBEpicSubZeroDriver.ProductCategories.Add(new ProductCategory { Category = categoryGolf, DisplayOrder = 1 }); - productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer() + productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), DisplayOrder = 1, @@ -10355,11 +10318,11 @@ public IList Products() #region category Soccer - var categorySoccer = this._ctx.Set().First(c => c.Alias == "Soccer"); + var categorySoccer = _ctx.Set().First(c => c.Alias == "Soccer"); #region product Nike Strike Football - var productNikeStrikeFootball = new Product() + var productNikeStrikeFootball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10382,25 +10345,21 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id, + HasTierPrices = true }; - productNikeStrikeFootball.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + AddProductPicture(productNikeStrikeFootball, "products_nike-strike-football.jpg"); - productNikeStrikeFootball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_nike-strike-football.jpg"), "image/png", GetSeName(productNikeStrikeFootball.Name)), - DisplayOrder = 1, - }); + productNikeStrikeFootball.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer() + productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), DisplayOrder = 1, }); - //attributes - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10408,7 +10367,7 @@ public IList Products() // Manufacturer -> Nike SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder ==20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() }); - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10417,30 +10376,15 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 12).Single() }); - #region tierPrieces - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 6, - Price = 26.90M - }); - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 12, - Price = 24.90M - }); - productNikeStrikeFootball.TierPrices.Add(new TierPrice() - { - Quantity = 24, - Price = 22.90M - }); - productNikeStrikeFootball.HasTierPrices = true; - #endregion tierPrieces + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 6, Price = 26.90M }); + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 12, Price = 24.90M }); + productNikeStrikeFootball.TierPrices.Add(new TierPrice { Quantity = 24, Price = 22.90M }); #endregion product Nike Strike Football #region product Evopower 5.3 Trainer HS Ball - var productNikeEvoPowerBall = new Product() + var productNikeEvoPowerBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10462,25 +10406,20 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); + AddProductPicture(productNikeEvoPowerBall, "product_nike-vopower-53-trainer-hs-ball.jpg"); - productNikeEvoPowerBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_nike-vopower-53-trainer-hs-ball.jpg"), "image/png", GetSeName(productNikeEvoPowerBall.Name)), - DisplayOrder = 1, - }); + productNikeEvoPowerBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer() + productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), DisplayOrder = 1, }); - //attributes - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10488,7 +10427,7 @@ public IList Products() // Manufacturer -> Nike SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() }); - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10501,7 +10440,7 @@ public IList Products() #region product Torfabrik official game ball - var productTorfabrikOfficialGameBall = new Product() + var productTorfabrikOfficialGameBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10523,49 +10462,24 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_white.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "white"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_red.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "red"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_yellow.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "yellow"), - DisplayOrder = 1, - }); - - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_blue.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "blue"), - DisplayOrder = 1, - }); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_white.png", "official-game-ball-white"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_red.png", "official-game-ball-red"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_yellow.png", "official-game-ball-yellow"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_blue.png", "official-game-ball-blue"); + AddProductPicture(productTorfabrikOfficialGameBall, "product_torfabrik-offizieller-spielball_green.png", "official-game-ball-green"); - productTorfabrikOfficialGameBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_torfabrik-offizieller-spielball_green.png"), "image/png", GetSeName(productTorfabrikOfficialGameBall.Name) + "green"), - DisplayOrder = 1, - }); + productTorfabrikOfficialGameBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer() + productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - //attributes - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10573,7 +10487,7 @@ public IList Products() // Manufacturer -> Adidas SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() }); - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10582,12 +10496,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - #endregion Torfabrik official game ball #region product Adidas TANGO SALA BALL - var productAdidasTangoSalaBall = new Product() + var productAdidasTangoSalaBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10609,61 +10522,26 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory() { Category = categorySoccer, DisplayOrder = 1 }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-white.png"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-white"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-yellow.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-yellow"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-red.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-red"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-green.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-green"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-gray.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-gray"), - DisplayOrder = 1, - }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-brown.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-brown"), - DisplayOrder = 1, - }); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-white.png", "adidas-tango-pasadena-ball-white"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-yellow.jpg", "adidas-tango-pasadena-ball-yellow"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-red.jpg", "adidas-tango-pasadena-ball-red"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-green.jpg", "adidas-tango-pasadena-ball-green"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-gray.jpg", "adidas-tango-pasadena-ball-gray"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-brown.jpg", "adidas-tango-pasadena-ball-brown"); + AddProductPicture(productAdidasTangoSalaBall, "product_adidas-tango-pasadena-ball-blue.jpg", "adidas-tango-pasadena-ball-blue"); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-blue.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-blue"), - DisplayOrder = 1, - }); + productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory { Category = categorySoccer, DisplayOrder = 1 }); - productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer() + productAdidasTangoSalaBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - //attributes - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10671,7 +10549,7 @@ public IList Products() // Manufacturer -> Adidas SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() }); - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -10686,11 +10564,11 @@ public IList Products() #region category Basketball - var categoryBasketball = this._ctx.Set().First(c => c.Alias == "Basketball"); + var categoryBasketball = _ctx.Set().First(c => c.Alias == "Basketball"); #region Wilson Evolution High School Game Basketball - var productEvolutionHighSchoolGameBasketball = new Product() + var productEvolutionHighSchoolGameBasketball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10713,49 +10591,29 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id, + HasTierPrices = true }; - productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory() { Category = categoryBasketball, DisplayOrder = 1 }); + AddProductPicture(productEvolutionHighSchoolGameBasketball, "product_evolution-high-school-game-basketball.jpg"); - productEvolutionHighSchoolGameBasketball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_evolution-high-school-game-basketball.jpg"), "image/png", GetSeName(productEvolutionHighSchoolGameBasketball.Name)), - DisplayOrder = 1, - }); + productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); - productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer() + productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, }); - #region tierPrieces - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 6, - Price = 24.90M - }); - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 12, - Price = 22.90M - }); - productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice() - { - Quantity = 24, - Price = 20.90M - }); - productEvolutionHighSchoolGameBasketball.HasTierPrices = true; - #endregion tierPrieces - + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 6, Price = 24.90M }); + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 12, Price = 22.90M }); + productEvolutionHighSchoolGameBasketball.TierPrices.Add(new TierPrice { Quantity = 24, Price = 20.90M }); #endregion Wilson Evolution High School Game Basketball - #region All Court Basketball - var productAllCourtBasketball = new Product() + var productAllCourtBasketball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10777,18 +10635,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productAllCourtBasketball.ProductCategories.Add(new ProductCategory() { Category = categoryBasketball, DisplayOrder = 1 }); + AddProductPicture(productAllCourtBasketball, "product_all-court-basketball.png"); - productAllCourtBasketball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_all-court-basketball.png"), "image/png", GetSeName(productAllCourtBasketball.Name)), - DisplayOrder = 1, - }); + productAllCourtBasketball.ProductCategories.Add(new ProductCategory { Category = categoryBasketball, DisplayOrder = 1 }); - productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer() + productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), DisplayOrder = 1, @@ -10800,11 +10654,11 @@ public IList Products() #region category sunglasses - var categorySunglasses = this._ctx.Set().First(c => c.Alias == "Sunglasses"); + var categorySunglasses = _ctx.Set().First(c => c.Alias == "Sunglasses"); #region product Top bar - var productRayBanTopBar = new Product() + var productRayBanTopBar = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10826,30 +10680,16 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productRayBanTopBar.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); - - productRayBanTopBar.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_1.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); - - productRayBanTopBar.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_2.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_1.jpg", "rayban-top-bar-1"); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_2.jpg", "rayban-top-bar-2"); + AddProductPicture(productRayBanTopBar, "product_RayBanTopBar_3.jpg", "rayban-top-bar-3"); - productRayBanTopBar.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_RayBanTopBar_3.jpg"), "image/png", GetSeName(productRayBanTopBar.Name)), - DisplayOrder = 1, - }); + productRayBanTopBar.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer() + productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), DisplayOrder = 1, @@ -10859,7 +10699,7 @@ public IList Products() #region product ORIGINAL WAYFARER AT COLLECTION - var productOriginalWayfarer = new Product() + var productOriginalWayfarer = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10881,48 +10721,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productOriginalWayfarer.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_1.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_2.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_3.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_gray-course-black"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_4.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_brown-course-havana"), - DisplayOrder = 1, - }); - - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_5.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_green-classic-havana-black"), - DisplayOrder = 1, - }); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_1.jpg", "wayfarer-blue-gray-classic-black-1"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_2.jpg", "wayfarer-blue-gray-classic-black-2"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_3.jpg", "wayfarer-gray-course-black"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_4.jpg", "wayfarer-brown-course-havana"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_5.jpg", "wayfarer-green-classic-havana-black"); + AddProductPicture(productOriginalWayfarer, "product_productOriginalWayfarer_6.jpg", "wayfarer-blue-gray-classic-black-3"); - productOriginalWayfarer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_productOriginalWayfarer_6.jpg"), "image/png", GetSeName(productOriginalWayfarer.Name) + "_blue-gray-classic-black"), - DisplayOrder = 1, - }); + productOriginalWayfarer.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer() + productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), DisplayOrder = 1, @@ -10932,7 +10743,7 @@ public IList Products() #region product Radar EV Prizm Sports Sunglasses - var productRadarEVPrizmSportsSunglasses = new Product() + var productRadarEVPrizmSportsSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10954,18 +10765,14 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); + AddProductPicture(productRadarEVPrizmSportsSunglasses, "product_radar_ev_prizm.jpg"); - productRadarEVPrizmSportsSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_radar_ev_prizm.jpg"), "image/png", GetSeName(productRadarEVPrizmSportsSunglasses.Name)), - DisplayOrder = 1, - }); + productRadarEVPrizmSportsSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); - productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer() + productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), DisplayOrder = 1, @@ -10975,7 +10782,7 @@ public IList Products() #region product Custom Flak Sunglasses - var productCustomFlakSunglasses = new Product() + var productCustomFlakSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10997,473 +10804,221 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory() { Category = categorySunglasses, DisplayOrder = 1 }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlakSunglasses.jpg", "custom_flak"); + AddProductPicture(productCustomFlakSunglasses, "productCustomFlakSunglasses_black_white.jpg", "custom_flak_black_white"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_gray.jpg", "custom_flak_matteblack_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_clear.jpg", "custom_flak_matteblack_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_jadeiridium.jpg", "custom_flak_matteblack_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_positiverediridium.jpg", "custom_flak_matteblack_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_rubyiridium.jpg", "custom_flak_matteblack_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_sapphireiridium.jpg", "custom_flak_matteblack_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_violetiridium.jpg", "custom_flak_matteblack_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_24kiridium.jpg", "custom_flak_matteblack_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_matteblack_fireiridium.jpg", "custom_flak_matteblack_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_24kiridium.jpg", "custom_flak_orangeflare_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_clear.jpg", "custom_flak_orangeflare_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_fireiridium.jpg", "custom_flak_orangeflare_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_gray.jpg", "custom_flak_orangeflare_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_jadeiridium.jpg", "custom_flak_orangeflare_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_positiverediridium.jpg", "custom_flak_orangeflare_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_rubyiridium.jpg", "custom_flak_orangeflare_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_sapphireiridium.jpg", "custom_flak_orangeflare_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_orangeflare_violetiridium.jpg", "custom_flak_orangeflare_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_24kiridium.jpg", "custom_flak_polishedwhite_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_clear.jpg", "custom_flak_polishedwhite_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_fireiridium.jpg", "custom_flak_polishedwhite_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_gray.jpg", "custom_flak_polishedwhite_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_jadeiridium.jpg", "custom_flak_polishedwhite_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_rubyiridium.jpg", "custom_flak_polishedwhite_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_sapphireiridium.jpg", "custom_flak_polishedwhite_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_violetiridium.jpg", "custom_flak_polishedwhite_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_polishedwhite_positiverediridium.jpg", "custom_flak_polishedwhite_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_24kiridium.jpg", "custom_flak_redline_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_clear.jpg", "custom_flak_redline_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_fireiridium.jpg", "custom_flak_redline_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_gray.jpg", "custom_flak_redline_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_jadeiridium.jpg", "custom_flak_redline_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_positiverediridium.jpg", "custom_flak_redline_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_rubyiridium.jpg", "custom_flak_redline_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_sapphireiridium.jpg", "custom_flak_redline_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_redline_violetiridium.jpg", "custom_flak_redline_violetiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_24kiridium.jpg", "custom_flak_skyblue_24kiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_clear.jpg", "custom_flak_skyblue_clear"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_fireiridium.jpg", "custom_flak_skyblue_fireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_gray.jpg", "custom_flak_skyblue_gray"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_jadeiridium.jpg", "custom_flak_skyblue_jadeiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_positiverediridium.jpg", "custom_flak_skyblue_positiverediridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_rubyiridium.jpg", "custom_flak_skyblue_rubyiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_sapphireiridium.jpg", "custom_flak_skyblue_sapphireiridium"); + AddProductPicture(productCustomFlakSunglasses, "product_CustomFlak_skyblue_violetiridium.jpg", "custom_flak_skyblue_violetiridium"); + + productCustomFlakSunglasses.ProductCategories.Add(new ProductCategory { Category = categorySunglasses, DisplayOrder = 1 }); + + productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlakSunglasses.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlakSunglasses.jpg"), + Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "productCustomFlakSunglasses_black_white.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "productCustomFlakSunglasses_black_white.jpg"), - DisplayOrder = 1, - }); + #endregion product Custom Flak Sunglasses - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + + #endregion category sunglasses + + #region category apple + + var categoryApple = _ctx.Set().First(c => c.Alias == "Apple"); + + #region product iphone plus + + var productIphoneplus = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "iPhone Plus", + IsEsd = false, + ShortDescription = "iPhone 7 dramatically improves the most important aspects of the iPhone experience. It introduces advanced new camera systems. The best performance and battery life ever in an iPhone. Immersive stereo speakers. The brightest, most colorful iPhone display. Splash and water resistance.1 And it looks every bit as powerful as it is. This is iPhone 7.", + FullDescription = "", + Sku = "P-2001", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "iPhone Plus", + Price = 878M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 9, + StockQuantity = 10000, + DisplayStockAvailability = true, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + IsFreeShipping = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productIphoneplus, "product_iphone-plus_all_colors.jpg", "iphone-plus-all-colors"); + AddProductPicture(productIphoneplus, "product_iphoneplus_1.jpg", "iphone-plus-default"); + AddProductPicture(productIphoneplus, "product_iphone-plus_red.jpg", "iphone-plus-red"); + AddProductPicture(productIphoneplus, "product_iphone-plus_silver.jpg", "iphone-plus-silver"); + AddProductPicture(productIphoneplus, "product_iphone-plus_black.jpg", "iphone-plus-black"); + AddProductPicture(productIphoneplus, "product_iphone-plus_rose.jpg", "iphone-plus-rose"); + AddProductPicture(productIphoneplus, "product_iphone-plus_gold.jpg", "iphone-plus-gold"); + + productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); + + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_jadeiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // offer type -> Permanent low price + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_positiverediridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 64gb + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_rubyiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 128gb + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_sapphireiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + #endregion product iphone plus + + #region product Watch Series 2 + + var productWatchSeries2 = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + ProductType = ProductType.SimpleProduct, + VisibleIndividually = false, + Name = "Watch Series 2", + IsEsd = false, + ShortDescription = "Live a better day. Built-in GPS. Water resistance to 50 meters.1 A lightning-fast dual‑core processor. And a display that’s two times brighter than before. Full of features that help you stay active, motivated, and connected, Apple Watch Series 2 is the perfect partner for a healthy life.", + FullDescription = "", + Sku = "P-2002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Watch Series 2", + Price = 299M, + OldPrice = 399M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; + + AddProductPicture(productWatchSeries2, "product_watchseries2_1.jpg", "watchseries-1"); + AddProductPicture(productWatchSeries2, "product_watchseries2_2.jpg", "watchseries-2"); + + productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); + + productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_fireiridium.jpg"), + Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + //attributes + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_24kiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // offer type -> offer of the day + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_clear.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 32gb + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_fireiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); + #endregion product Watch Series 2 + + #region product Airpods - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_positiverediridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_violetiridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_fireiridium.jpg"), - DisplayOrder = 1, - }); - - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_rubyiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_positiverediridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_fireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_violetiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_fireiridium.jpg"), - DisplayOrder = 1, - }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_gray.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_jadeiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_sapphireiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_skyblue_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_skyblue_violetiridium.jpg"), - DisplayOrder = 1, - }); - - - - productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), - DisplayOrder = 1, - }); - - #endregion product Custom Flak Sunglasses - - - - #endregion category sunglasses - - #region category apple - - var categoryApple = this._ctx.Set().First(c => c.Alias == "Apple"); - - #region product iphone plus - - var productIphoneplus = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "iPhone Plus", - IsEsd = false, - ShortDescription = "iPhone 7 dramatically improves the most important aspects of the iPhone experience. It introduces advanced new camera systems. The best performance and battery life ever in an iPhone. Immersive stereo speakers. The brightest, most colorful iPhone display. Splash and water resistance.1 And it looks every bit as powerful as it is. This is iPhone 7.", - FullDescription = "", - Sku = "P-2001", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "iPhone Plus", - Price = 878M, - IsGiftCard = false, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 9, - StockQuantity = 10000, - DisplayStockAvailability = true, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - IsFreeShipping = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; - - productIphoneplus.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_all_colors.jpg"), "image/png", GetSeName(productIphoneplus.Name)), - DisplayOrder = 1, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_1.jpg"), "image/png", GetSeName(productIphoneplus.Name)), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_red.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-red"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_silver.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-silver"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_black.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-black"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_rose.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-rose"), - DisplayOrder = 2, - }); - - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_gold.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-gold"), - DisplayOrder = 2, - }); - - //attributes - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // offer type -> Permanent low price - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - }); - - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // storage capacity -> 64gb - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - }); - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // storage capacity -> 128gb - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() - }); - productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // operating system -> ios - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - }); - - #endregion product iphone plus - - #region product Watch Series 2 - - var productWatchSeries2 = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = false, - Name = "Watch Series 2", - IsEsd = false, - ShortDescription = "Live a better day. Built-in GPS. Water resistance to 50 meters.1 A lightning-fast dual‑core processor. And a display that’s two times brighter than before. Full of features that help you stay active, motivated, and connected, Apple Watch Series 2 is the perfect partner for a healthy life.", - FullDescription = "", - Sku = "P-2002", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Watch Series 2", - Price = 299M, - OldPrice = 399M, - IsGiftCard = false, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; - - productWatchSeries2.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); - - productWatchSeries2.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_1.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 1, - }); - - productWatchSeries2.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 2, - }); - - productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - DisplayOrder = 1, - }); - - //attributes - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // offer type -> offer of the day - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() - }); - - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // storage capacity -> 32gb - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); - - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // operating system -> ios - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - }); - - - #endregion product Watch Series 2 - - #region product Airpods - - var productAirpods = new Product() + var productAirpods = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11485,48 +11040,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productAirpods.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + AddProductPicture(productAirpods, "products_airpods_white.jpg", "airpods-white"); + AddProductPicture(productAirpods, "products_airpods_turquoise.jpg", "airpods-turquoise"); + AddProductPicture(productAirpods, "products_airpods_lightblue.jpg", "airpods-lightblue"); + AddProductPicture(productAirpods, "products_airpods_rose.jpg", "airpods-rose"); + AddProductPicture(productAirpods, "products_airpods_gold.jpg", "airpods-gold"); + AddProductPicture(productAirpods, "products_airpods_mint.jpg", "airpods-mint"); - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAirpods.Name) + "-white"), - DisplayOrder = 1, - }); - - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_turquoise.jpg"), "image/png", GetSeName(productAirpods.Name) + "-turquoise"), - DisplayOrder = 2, - }); - - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_lightblue.jpg"), "image/png", GetSeName(productAirpods.Name) + "-lightblue"), - DisplayOrder = 3, - }); - - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_rose.jpg"), "image/png", GetSeName(productAirpods.Name) + "-rose"), - DisplayOrder = 4, - }); - - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_gold.jpg"), "image/png", GetSeName(productAirpods.Name) + "-gold"), - DisplayOrder = 5, - }); - - productAirpods.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_mint.jpg"), "image/png", GetSeName(productAirpods.Name) + "-mint"), - DisplayOrder = 6, - }); + productAirpods.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productAirpods.ProductManufacturers.Add(new ProductManufacturer() + productAirpods.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 7, @@ -11536,7 +11062,7 @@ public IList Products() #region product Ultimate Apple Pro Hipster Bundle - var productAppleProHipsterBundle = new Product() + var productAppleProHipsterBundle = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -11559,45 +11085,21 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), + DeliveryTimeId = thirdDeliveryTime.Id, BundleTitleText = "Bundle includes", BundlePerItemPricing = true, BundlePerItemShoppingCart = true }; - - productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ultimate-apple-pro-hipster-bundle.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 1, - }); - - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); - - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + AddProductPicture(productAppleProHipsterBundle, "product_ultimate-apple-pro-hipster-bundle.jpg", "apple-pro-hipster-bundle"); + AddProductPicture(productAppleProHipsterBundle, "products_airpods_white.jpg", "bundle-airpods-white"); + AddProductPicture(productAppleProHipsterBundle, "product_watchseries2_2.jpg", "bundle-watchseries"); + AddProductPicture(productAppleProHipsterBundle, "product_iphoneplus_2.jpg", "bundle-iphoneplus"); + AddProductPicture(productAppleProHipsterBundle, "category_apple.png", "bundle-apple"); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); - - productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer() + productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, @@ -11607,7 +11109,7 @@ public IList Products() #region product 9,7 iPad - var product97ipad = new Product() + var product97ipad = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11634,87 +11136,31 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - product97ipad.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); - - #region pictures - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_1.jpg"), "image/png", GetSeName(product97ipad.Name)), - DisplayOrder = 1, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_2.jpg"), "image/png", GetSeName(product97ipad.Name)), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-yellow.jpg"), "image/png", GetSeName(product97ipad.Name) + "-yellow"), - DisplayOrder = 2, - }); - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-turquoise.jpg"), "image/png", GetSeName(product97ipad.Name) + "-turquoise"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-lightblue.jpg"), "image/png", GetSeName(product97ipad.Name) + "-lightblue"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-purple.jpg"), "image/png", GetSeName(product97ipad.Name) + "-purple"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-mint.jpg"), "image/png", GetSeName(product97ipad.Name) + "-mint"), - DisplayOrder = 2, - }); + AddProductPicture(product97ipad, "product_ipad_1.jpg", "ipad-1"); + AddProductPicture(product97ipad, "product_ipad_2.jpg", "ipad-2"); + AddProductPicture(product97ipad, "product_97-ipad-yellow.jpg", "ipad-yellow"); + AddProductPicture(product97ipad, "product_97-ipad-turquoise.jpg", "ipad-turquoise"); + AddProductPicture(product97ipad, "product_97-ipad-lightblue.jpg", "ipad-lightblue"); + AddProductPicture(product97ipad, "product_97-ipad-purple.jpg", "ipad-purple"); + AddProductPicture(product97ipad, "product_97-ipad-mint.jpg", "ipad-mint"); + AddProductPicture(product97ipad, "product_97-ipad-rose.jpg", "ipad-rose"); + AddProductPicture(product97ipad, "product_97-ipad-spacegray.jpg", "ipad-spacegray"); + AddProductPicture(product97ipad, "product_97-ipad-gold.jpg", "ipad-gold"); + AddProductPicture(product97ipad, "product_97-ipad-silver.jpg", "ipad-silver"); - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-rose.jpg"), "image/png", GetSeName(product97ipad.Name) + "-rose"), - DisplayOrder = 2, - }); + product97ipad.ProductCategories.Add(new ProductCategory { Category = categoryApple, DisplayOrder = 1 }); - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-spacegray.jpg"), "image/png", GetSeName(product97ipad.Name) + "-spacegray"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-gold.jpg"), "image/png", GetSeName(product97ipad.Name) + "-gold"), - DisplayOrder = 2, - }); - - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-silver.jpg"), "image/png", GetSeName(product97ipad.Name) + "-silver"), - DisplayOrder = 2, - }); - #endregion pictures - - product97ipad.ProductManufacturers.Add(new ProductManufacturer() + product97ipad.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), DisplayOrder = 1, }); - #region attributes - //attributes - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11723,7 +11169,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11731,7 +11177,7 @@ public IList Products() // storage capacity -> 64gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11739,7 +11185,7 @@ public IList Products() // storage capacity -> 128gb SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 27).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -11747,20 +11193,18 @@ public IList Products() // operating system -> ios SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - #endregion attributes #endregion product 9,7 iPad - #endregion category apple #region category Gift Cards - var categoryGiftCards = this._ctx.Set().First(c => c.Alias == "Gift Cards"); + var categoryGiftCards = _ctx.Set().First(c => c.Alias == "Gift Cards"); #region product10GiftCard - var product10GiftCard = new Product() + var product10GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11781,28 +11225,17 @@ public IList Products() StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - DisplayOrder = 1 - + DisplayOrder = 1 }; - product10GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - //var productTag = _productTagRepository.Table.Where(pt => pt.Name == "gift").FirstOrDefault(); - //productTag.ProductCount++; - //productTag.Products.Add(product5GiftCard); - //_productTagRepository.Update(productTag); - - product10GiftCard.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_10.png"), "image/png", GetSeName(product10GiftCard.Name)), - //DisplayOrder = 1, - }); + AddProductPicture(product10GiftCard, "product_gift_card_10.png"); + product10GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); #endregion product10GiftCard #region product25GiftCard - var product25GiftCard = new Product() + var product25GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11827,19 +11260,14 @@ public IList Products() DisplayOrder = 2 }; - product25GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - product25GiftCard.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_25.png"), "image/png", GetSeName(product25GiftCard.Name)), - //DisplayOrder = 2, - }); + AddProductPicture(product25GiftCard, "product_gift_card_25.png"); + product25GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); #endregion product25GiftCard #region product50GiftCard - var product50GiftCard = new Product() + var product50GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11864,19 +11292,14 @@ public IList Products() DisplayOrder = 3 }; - product50GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - product50GiftCard.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_50.png"), "image/png", GetSeName(product50GiftCard.Name)), - //DisplayOrder = 3, - }); + AddProductPicture(product50GiftCard, "product_gift_card_50.png"); + product50GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); #endregion product50GiftCard #region product100GiftCard - var product100GiftCard = new Product() + var product100GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11901,13 +11324,8 @@ public IList Products() DisplayOrder = 4, }; - product100GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); - - product100GiftCard.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_100.png"), "image/png", GetSeName(product100GiftCard.Name)), - //DisplayOrder = 4, - }); + AddProductPicture(product100GiftCard, "product_gift_card_100.png"); + product100GiftCard.ProductCategories.Add(new ProductCategory { Category = categoryGiftCards, DisplayOrder = 1 }); #endregion product100GiftCard @@ -11915,13 +11333,13 @@ public IList Products() #region category books - var categorySpiegelBestseller = this._ctx.Set().First(c => c.Alias == "SPIEGEL-Bestseller"); - var categoryCookAndEnjoy = this._ctx.Set().First(c => c.Alias == "Cook and enjoy"); - var categoryBooks = this._ctx.Set().First(c => c.Alias == "Books"); + var categorySpiegelBestseller = _ctx.Set().First(c => c.Alias == "SPIEGEL-Bestseller"); + var categoryCookAndEnjoy = _ctx.Set().First(c => c.Alias == "Cook and enjoy"); + var categoryBooks = _ctx.Set().First(c => c.Alias == "Books"); #region productBooksUberMan - var productBooksUberMan = new Product() + var productBooksUberMan = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11943,42 +11361,36 @@ public IList Products() IsShipEnabled = true }; - productBooksUberMan.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - - //pictures - productBooksUberMan.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000932_uberman-der-roman.jpeg"), "image/jpeg", GetSeName(productBooksUberMan.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksUberMan, "0000932_uberman-der-roman.jpeg"); + productBooksUberMan.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - //attributes - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); + #endregion productBooksUberMan #region productBooksGefangeneDesHimmels - var productBooksGefangeneDesHimmels = new Product() + var productBooksGefangeneDesHimmels = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -11998,20 +11410,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - //pictures - productBooksGefangeneDesHimmels.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000935_der-gefangene-des-himmels-roman_300.jpeg"), "image/jpeg", GetSeName(productBooksGefangeneDesHimmels.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksGefangeneDesHimmels, "0000935_der-gefangene-des-himmels-roman_300.jpeg"); + productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - //attributes - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12020,7 +11425,7 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12028,7 +11433,7 @@ public IList Products() // Category -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12041,7 +11446,7 @@ public IList Products() #region productBooksBestGrillingRecipes - var productBooksBestGrillingRecipes = new Product() + var productBooksBestGrillingRecipes = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12061,20 +11466,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; + DeliveryTimeId = firstDeliveryTime.Id + }; - productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); + AddProductPicture(productBooksBestGrillingRecipes, "product_bestgrillingrecipes.jpg"); + productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - //pictures - productBooksBestGrillingRecipes.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_bestgrillingrecipes.jpg"), "image/jpeg", GetSeName(productBooksBestGrillingRecipes.Name)), - DisplayOrder = 1, - }); - - //attributes - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12082,7 +11480,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12090,7 +11488,7 @@ public IList Products() // Category -> cook & bake SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12103,7 +11501,7 @@ public IList Products() #region productBooksCookingForTwo - var productBooksCookingForTwo = new Product() + var productBooksCookingForTwo = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12123,20 +11521,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() - }; - - productBooksCookingForTwo.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); + DeliveryTimeId = secondDeliveryTime.Id + }; - //pictures - productBooksCookingForTwo.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_cookingfortwo.jpg"), "image/jpeg", GetSeName(productBooksCookingForTwo.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksCookingForTwo, "product_cookingfortwo.jpg"); + productBooksCookingForTwo.ProductCategories.Add(new ProductCategory { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); - //attributes - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12144,7 +11535,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12152,7 +11543,7 @@ public IList Products() // Category -> cook & bake SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12165,7 +11556,7 @@ public IList Products() #region productBooksAutosDerSuperlative - var productBooksAutosDerSuperlative = new Product() + var productBooksAutosDerSuperlative = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12185,20 +11576,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; + DeliveryTimeId = thirdDeliveryTime.Id + }; - productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + AddProductPicture(productBooksAutosDerSuperlative, "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"); + productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //pictures - productBooksAutosDerSuperlative.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"), "image/jpeg", GetSeName(productBooksAutosDerSuperlative.Name)), - DisplayOrder = 1, - }); - - //attributes - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12206,7 +11590,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12214,7 +11598,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12223,12 +11607,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - #endregion productBooksAutosDerSuperlative #region productBooksBildatlasMotorraeder - var productBooksBildatlasMotorraeder = new Product() + var productBooksBildatlasMotorraeder = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12248,20 +11631,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - //pictures - productBooksBildatlasMotorraeder.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"), "image/jpeg", GetSeName(productBooksBildatlasMotorraeder.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksBildatlasMotorraeder, "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"); + productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //attributes - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12269,7 +11645,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12277,7 +11653,7 @@ public IList Products() // Category -> non-fiction SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12290,7 +11666,7 @@ public IList Products() #region productBooksAutoBuch - var productBooksAutoBuch = new Product() + var productBooksAutoBuch = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12310,20 +11686,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksAutoBuch.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - //pictures - productBooksAutoBuch.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"), "image/jpeg", GetSeName(productBooksAutoBuch.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksAutoBuch, "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"); + productBooksAutoBuch.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //attributes - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12331,7 +11700,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12339,7 +11708,7 @@ public IList Products() // Category -> non-fiction SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12352,7 +11721,7 @@ public IList Products() #region productBooksFastCars - var productBooksFastCars = new Product() + var productBooksFastCars = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12372,20 +11741,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksFastCars.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - //pictures - productBooksFastCars.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000946_fast-cars-bildkalender-2013_300.jpeg"), "image/jpeg", GetSeName(productBooksFastCars.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksFastCars, "0000946_fast-cars-bildkalender-2013_300.jpeg"); + productBooksFastCars.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); - //attributes - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12393,7 +11755,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12401,7 +11763,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12414,489 +11776,68 @@ public IList Products() #region productBooksMotorradAbenteuer - var productBooksMotorradAbenteuer = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Motorcycle Adventures: Riding for travel enduros", - ShortDescription = "Hardcover", - FullDescription = "

Modern travel enduro bikes are ideal for adventure travel. Their technique is complex, their weight considerably. The driving behavior changes depending on the load and distance.

Before the tour starts, you should definitely attend a training course. This superbly illustrated book presents practical means of many informative series photos the right off-road driving in mud and sand, gravel and rock with and without luggage. In addition to the driving course full of information and tips on choosing the right motorcycle for travel planning and practical issues may be on the way.

", - Sku = "P-1011", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Motorcycle Adventures", - Price = 24.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() - }; - - productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); - - //pictures - productBooksMotorradAbenteuer.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"), "image/jpeg", GetSeName(productBooksMotorradAbenteuer.Name)), - DisplayOrder = 1, - }); - - //attributes - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Edition -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> cars - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - }); - productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Language -> German - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); - - #endregion productBooksMotorradAbenteuer - - #endregion category books - - #region computer - - // var categoryComputer = this._ctx.Set().First(c => c.Alias == "Computers"); - // var categoryNotebooks = this._ctx.Set().First(c => c.Alias == "Notebooks"); - // var categoryDesktops = this._ctx.Set().First(c => c.Alias == "Desktops"); - - //#region productComputerDellInspiron23 - - //var productComputerDellInspiron23 = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Dell Inspiron One 23", - // ShortDescription = "This 58 cm (23'')-All-in-One PC with Full HD, Windows 8 and powerful Intel ® Core ™ processor third generation allows practical interaction with a touch screen.", - // FullDescription = "

Ultra high performance all-in-one i7 PC with Windows 8, Intel ® Core ™ processor, huge 2TB hard drive and Blu-Ray drive.

Intel® Core™ i7-3770S Processor ( 3,1 GHz, 6 MB Cache)
Windows 8 64bit , english
8 GB1 DDR3 SDRAM at 1600 MHz
2 TB-Serial ATA-Harddisk (7.200 rot/min)
1GB AMD Radeon HD 7650

", - // Sku = "P-1012", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Dell Inspiron One 23", - // Price = 589.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerDellInspiron23.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); - - //#region pictures - - ////pictures - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 1, - //}); - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000954_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 2, - //}); - //productComputerDellInspiron23.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000956_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellInspiron23.Name)), - // DisplayOrder = 3, - //}); - - //#endregion pictures - - //#region manufacturer - - ////manufacturer - //productComputerDellInspiron23.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - // DisplayOrder = 1, - //}); - - // #endregion manufacturer - - // #region SpecificationAttributes - // //attributes - // productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - // { - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 1, - // // CPU -> Intel - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - // }); - // productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 2, - // // RAM -> 4 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 4).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 3, - // // Harddisk-Typ / HDD - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 16).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // // Harddisk-Capacity / 750 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 3).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() - //}); - //productComputerDellInspiron23.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 5, - // // OS / Windows 7 32 Bit - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //#endregion SpecificationAttributes - - //#endregion productComputerDellInspiron23 - - //#region productComputerDellOptiplex3010 - - //var productComputerDellOptiplex3010 = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Dell Optiplex 3010 DT Base", - // ShortDescription = "SPECIAL OFFER: Extra 50 € discount on all Dell OptiPlex desktops from a value of € 549. Online Coupon:? W8DWQ0ZRKTM1, valid until 04/12/2013.", - // FullDescription = "

Also included in this system include To change these selections, the

1 Year Basic Service - On-Site NBD - No Upgrade Selected
No asset tag required

The following options are default selections included with your order.
German (QWERTY) Dell KB212-B Multimedia USB Keyboard Black
X11301001
WINDOWS LIVE
OptiPlex ™ order - Germany
OptiPlex ™ Intel ® Core ™ i3 sticker
Optical software is not required, operating system software sufficiently

", - // Sku = "P-1013", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Dell Optiplex 3010 DT Base", - // Price = 419.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerDellOptiplex3010.ProductCategories.Add(new ProductCategory() { Category = categoryDesktops, DisplayOrder = 1 }); - - //#region pictures - - ////pictures - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dellinspiron23.png"), "image/png", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 1, - //}); - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000954_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 2, - //}); - //productComputerDellOptiplex3010.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000956_dell-inspiron-one-23.jpeg"), "image/jpeg", GetSeName(productComputerDellOptiplex3010.Name)), - // DisplayOrder = 3, - //}); - - //#endregion pictures - - //#region manufacturer - - ////manufacturer - //productComputerDellOptiplex3010.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Dell").Single(), - // DisplayOrder = 1, - //}); - - //#endregion manufacturer - - //#region SpecificationAttributes - ////attributes - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 1, - // // CPU -> Intel - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 2, - // // RAM -> 4 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 4).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 3, - // // Harddisk-Typ / HDD - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 16).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 4, - // // Harddisk-Capacity / 750 GB - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 3).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() - //}); - //productComputerDellOptiplex3010.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 5, - // // OS / Windows 7 32 Bit - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 4).Single() - //}); - //#endregion SpecificationAttributes - - //#endregion productComputerDellOptiplex3010 - - //#region productComputerAcerAspireOne - //var productComputerAcerAspireOne = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Acer Aspire One 8.9\" Mini-Notebook Case - (Black)", - // ShortDescription = "Acer Aspire One 8.9\" Mini-Notebook and 6 Cell Battery model (AOA150-1447)", - // FullDescription = "

Acer Aspire One 8.9" Memory Foam Pouch is the perfect fit for Acer Aspire One 8.9". This pouch is made out of premium quality shock absorbing memory form and it provides extra protection even though case is very light and slim. This pouch is water resistant and has internal supporting bands for Acer Aspire One 8.9". Made In Korea.

", - // Sku = "P-1014", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Acer Aspire One 8.9", - // ShowOnHomePage = true, - // Price = 210.6M, - // IsShipEnabled = true, - // Weight = 2, - // Length = 2, - // Width = 2, - // Height = 3, - // ManageInventoryMethod = ManageInventoryMethod.ManageStock, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // DisplayStockAvailability = true, - // LowStockActivity = LowStockActivity.DisableBuyButton, - // BackorderMode = BackorderMode.NoBackorders, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - //}; - - // productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryComputer, DisplayOrder = 1 }); - // productComputerAcerAspireOne.ProductCategories.Add(new ProductCategory() { Category = categoryNotebooks, DisplayOrder = 1 }); - - //#region manufacturer - - ////manufacturer - //productComputerAcerAspireOne.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Acer").Single(), - // DisplayOrder = 1, - //}); - - //#endregion manufacturer - - //#region tierPrieces - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 2, - // Price = 205 - //}); - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 5, - // Price = 189 - //}); - //productComputerAcerAspireOne.TierPrices.Add(new TierPrice() - //{ - // Quantity = 10, - // Price = 155 - //}); - //productComputerAcerAspireOne.HasTierPrices = true; - //#endregion tierPrieces - - //#region pictures - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_aceraspiresl1500.png"), "image/png", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 1, - //}); - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "01-12Hand_Aspire1.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 2, - //}); - //productComputerAcerAspireOne.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "58_00007561.jpg"), "image/jpeg", GetSeName(productComputerAcerAspireOne.Name)), - // DisplayOrder = 3, - //}); - - //#endregion tierPrieces - - //#endregion productComputerAcerAspireOne - - #endregion computer - - #region Smartphones - - // var categoryCellPhones = this._ctx.Set().First(c => c.Alias == "Cell phones"); - - //#region productSmartPhonesAppleIphone - - //var productSmartPhonesAppleIphone = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Name = "Apple iPhone 6", - // ShortDescription = "The biggest thing to happen to iPhone since iPhone.", - // FullDescription = "

Available in silver, gold, and space gray, iPhone 6 Plus features an A8 chip, Touch ID, faster LTE wireless, a new 8MP iSight camera with Focus Pixels, and iOS 8.

Weight and Dimensions: Height: 6.22 inches (158.1 mm), Width: 3.06 inches (77.8 mm), Depth: 0.28 inch (7.1 mm), Weight: 6.07 ounces (172 grams).

  • A8 chip with 64-bit architecture. M8 motion coprocessor.
  • New 8-megapixel iSight camera with 1.5µ pixels. Autofocus with Focus Pixels.
  • 1080p HD video recording (30 fps or 60 fps).
  • Retina HD display. 4.7-inch (diagonal) LED-backlit widescreen Multi Touch display with IPS technology.
  • 1334-by-750-pixel resolution at 326 ppi. 1400:1 contrast ratio (typical). 500 cd/m2 max brightness (typical).
  • Fingerprint identity sensor built into the Home button.
  • 802.11a/b/g/n/ac Wi-Fi. Bluetooth 4.0 wireless technology. NFC.
  • RAM 1GB, Internal storage 16GB.
  • Colors: Silver, Gold, Space Gray.

", - // Sku = "Apple-1001", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Apple iPhone 6", - // ShowOnHomePage = true, - // Price = 579.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - //}; - - // productSmartPhonesAppleIphone.ProductCategories.Add(new ProductCategory() { Category = categoryCellPhones, DisplayOrder = 1 }); - - //#region pictures - - ////pictures - //productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000789-apple-iphone.jpg"), "image/jpeg", GetSeName(productSmartPhonesAppleIphone.Name)), - // DisplayOrder = 1, - //}); - //productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000785-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - // DisplayOrder = 2, - //}); - //productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000786-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - // DisplayOrder = 3, - //}); - //productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000787-apple-iphone.jpg"), "image/jpeg", GetSeName(productSmartPhonesAppleIphone.Name)), - // DisplayOrder = 4, - //}); - //productSmartPhonesAppleIphone.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "0000788-apple-iphone.png"), "image/png", GetSeName(productSmartPhonesAppleIphone.Name)), - // DisplayOrder = 5, - //}); - - - - //#endregion pictures - - //#region manufacturer - - ////manufacturer - //productSmartPhonesAppleIphone.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - // DisplayOrder = 1, - //}); - - //#endregion manufacturer + var productBooksMotorradAbenteuer = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Motorcycle Adventures: Riding for travel enduros", + ShortDescription = "Hardcover", + FullDescription = "

Modern travel enduro bikes are ideal for adventure travel. Their technique is complex, their weight considerably. The driving behavior changes depending on the load and distance.

Before the tour starts, you should definitely attend a training course. This superbly illustrated book presents practical means of many informative series photos the right off-road driving in mud and sand, gravel and rock with and without luggage. In addition to the driving course full of information and tips on choosing the right motorcycle for travel planning and practical issues may be on the way.

", + Sku = "P-1011", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Motorcycle Adventures", + Price = 24.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = secondDeliveryTime.Id + }; - //#region SpecificationAttributes - ////attributes - //productSmartPhonesAppleIphone.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 1, - // // housing > alu - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 4).Single() - //}); - //productSmartPhonesAppleIphone.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 2, - // // manufacturer > apple - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - //}); - //productSmartPhonesAppleIphone.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - //{ - // AllowFiltering = true, - // ShowOnProductPage = true, - // DisplayOrder = 5, - // // OS / Windows 7 32 Bit - // SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 5).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - //}); - // #endregion SpecificationAttributes + AddProductPicture(productBooksMotorradAbenteuer, "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"); + productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory { Category = categoryBooks, DisplayOrder = 1 }); + + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + }); + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cars + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() + }); + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + }); - //#endregion productSmartPhonesAppleIphone + #endregion productBooksMotorradAbenteuer - #endregion Smartphones + #endregion category books #region Instant Download Music / Digital Products - var categoryDigitalProducts = this._ctx.Set().First(c => c.Alias == "Digital Products"); + var categoryDigitalProducts = _ctx.Set().First(c => c.Alias == "Digital Products"); #region product Books Stone of the Wise - var productBooksStoneOfTheWise = new Product() + var productBooksStoneOfTheWise = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12917,33 +11858,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = "application/pdf", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "Stone_of_the_wise_preview.pdf") - }, - Extension = ".pdf", - Filename = "Stone_of_the_wise_preview", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); - - //pictures - productBooksStoneOfTheWise.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "stone_of_wisdom.jpg"), "image/jpeg", GetSeName(productBooksStoneOfTheWise.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productBooksStoneOfTheWise, "stone_of_wisdom.jpg"); + productBooksStoneOfTheWise.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - //attributes - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12951,7 +11872,7 @@ public IList Products() // Edition -> bound SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12959,7 +11880,7 @@ public IList Products() // Category -> cars SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -12968,7 +11889,6 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 12).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - #endregion product Books Stone of the Wise @@ -12995,38 +11915,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "vivaldi-four-seasons-spring.mp3") - }, - Extension = ".mp3", - Filename = "vivaldi-four-seasons-spring", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); + AddProductPicture(productInstantDownloadVivaldi, "vivaldi.jpg"); + productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - #region pictures - - //pictures - productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "vivaldi.jpg"), "image/jpeg", GetSeName(productInstantDownloadVivaldi.Name)), - DisplayOrder = 1, - }); - - #endregion pictures - - #region SpecificationAttributes - //attributes - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13034,7 +11929,7 @@ public IList Products() // mp3 quality > 320 kbit/S SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13043,13 +11938,11 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - #endregion SpecificationAttributes - #endregion Antonio Vivildi: then spring #region Beethoven für Elise - var productInstantDownloadBeethoven = new Product() + var productInstantDownloadBeethoven = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13071,38 +11964,13 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsDownload = true, - HasSampleDownload = true, - SampleDownload = new Download() - { - DownloadGuid = Guid.NewGuid(), - ContentType = "audio/mp3", - MediaStorage = new MediaStorage - { - Data = File.ReadAllBytes(sampleDownloadsPath + "beethoven-fur-elise.mp3") - }, - Extension = ".mp3", - Filename = "beethoven-fur-elise.mp3", - IsNew = true, - UpdatedOnUtc = DateTime.UtcNow - } + HasSampleDownload = true }; - productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory() { Category = categoryDigitalProducts, DisplayOrder = 1 }); - - #region pictures - - //pictures - productInstantDownloadBeethoven.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "Beethoven.jpg"), "image/jpeg", GetSeName(productInstantDownloadBeethoven.Name)), - DisplayOrder = 1, - }); - - #endregion pictures + AddProductPicture(productInstantDownloadBeethoven, "Beethoven.jpg"); + productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory { Category = categoryDigitalProducts, DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13110,7 +11978,7 @@ public IList Products() // mp3 quality > 320 kbit/S SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13119,20 +11987,17 @@ public IList Products() SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() }); - #endregion SpecificationAttributes - #endregion Beethoven für Elise #endregion Instant Download Music #region watches - var categoryWatches = this._ctx.Set().First(c => c.Alias == "Watches"); - + var categoryWatches = _ctx.Set().First(c => c.Alias == "Watches"); #region productTRANSOCEANCHRONOGRAPH - var productTRANSOCEANCHRONOGRAPH = new Product() + var productTRANSOCEANCHRONOGRAPH = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13154,36 +12019,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures - - //pictures - productTRANSOCEANCHRONOGRAPH.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_transocean-chronograph.jpg"), "image/png", GetSeName(productTRANSOCEANCHRONOGRAPH.Name)), - DisplayOrder = 1, - }); - - #endregion pictures + AddProductPicture(productTRANSOCEANCHRONOGRAPH, "product_transocean-chronograph.jpg"); + productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - #region manufacturer - - //manufacturer - productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer() + productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Breitling").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13191,7 +12039,7 @@ public IList Products() // offer > promotion SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13199,7 +12047,7 @@ public IList Products() // manufacturer > Breitling SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 18).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13207,7 +12055,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13215,7 +12063,7 @@ public IList Products() // material -> leather SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13223,7 +12071,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13231,7 +12079,7 @@ public IList Products() // movement -> mechanical, self winding SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13239,7 +12087,7 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13247,13 +12095,12 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - #endregion SpecificationAttributes #endregion productTRANSOCEANCHRONOGRAPH #region productTissotT-TouchExpertSolar - var productTissotTTouchExpertSolar = new Product() + var productTissotTTouchExpertSolar = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13275,42 +12122,21 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures - - //pictures - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), - DisplayOrder = 1, - }); - - productTissotTTouchExpertSolar.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_tissot-t-touch-expert-solar-t091_2.jpg"), "image/png", GetSeName(productTissotTTouchExpertSolar.Name)), - DisplayOrder = 1, - }); - - #endregion pictures + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar.jpg", "tissot-1"); + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar-t091_2.jpg", "tissot-2"); - #region manufacturer + productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //manufacturer - productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer() + productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Tissot").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13318,7 +12144,7 @@ public IList Products() // offer > best price SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13326,7 +12152,7 @@ public IList Products() // manufacturer > Tissot SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 17).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13334,7 +12160,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13342,7 +12168,7 @@ public IList Products() // material -> silicone SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13350,7 +12176,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13358,7 +12184,7 @@ public IList Products() // movement -> Automatic, self-winding SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13366,7 +12192,7 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13374,13 +12200,12 @@ public IList Products() // closure -> thorn close SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - #endregion SpecificationAttributes #endregion productTissotT-TouchExpertSolar #region productSeikoSRPA49K1 - var productSeikoSRPA49K1 = new Product() + var productSeikoSRPA49K1 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13402,36 +12227,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures - - //pictures - productSeikoSRPA49K1.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_SeikoSRPA49K1.jpg"), "image/png", GetSeName(productSeikoSRPA49K1.Name)), - DisplayOrder = 1, - }); - - #endregion pictures - - #region manufacturer + AddProductPicture(productSeikoSRPA49K1, "product_SeikoSRPA49K1.jpg"); + productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //manufacturer - productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer() + productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Seiko").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13439,7 +12247,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13447,7 +12255,7 @@ public IList Products() // material -> stainless steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13455,7 +12263,7 @@ public IList Products() // manufacturer > Seiko SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 16).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13463,7 +12271,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13471,7 +12279,7 @@ public IList Products() // movement -> quarz SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13479,7 +12287,7 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13487,14 +12295,13 @@ public IList Products() // diameter -> 44mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - #endregion SpecificationAttributes #endregion productSeikoSRPA49K1 #region productWatchesCertinaDSPodiumBigSize - var productWatchesCertinaDSPodiumBigSize = new Product() + var productWatchesCertinaDSPodiumBigSize = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13515,36 +12322,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; - - productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory() { Category = categoryWatches, DisplayOrder = 1 }); - - #region pictures - - //pictures - productWatchesCertinaDSPodiumBigSize.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_certina_ds_podium_big.png"), "image/png", GetSeName(productWatchesCertinaDSPodiumBigSize.Name)), - DisplayOrder = 1, - }); - - #endregion pictures + DeliveryTimeId = thirdDeliveryTime.Id + }; - #region manufacturer + AddProductPicture(productWatchesCertinaDSPodiumBigSize, "product_certina_ds_podium_big.png"); + productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory { Category = categoryWatches, DisplayOrder = 1 }); - //manufacturer - productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer() + productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = _ctx.Set().Where(c => c.Name == "Certina").Single(), DisplayOrder = 1, }); - #endregion manufacturer - - #region SpecificationAttributes - //attributes - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13552,7 +12342,7 @@ public IList Products() // housing > steel SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13560,7 +12350,7 @@ public IList Products() // material -> leather SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13568,7 +12358,7 @@ public IList Products() // manufacturer > Certina SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 14).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13576,7 +12366,7 @@ public IList Products() // Gender -> gentlemen SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13584,7 +12374,7 @@ public IList Products() // movement -> quarz SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13592,7 +12382,7 @@ public IList Products() // closure -> folding clasp SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, @@ -13600,7 +12390,6 @@ public IList Products() // diameter -> 40mm SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() }); - #endregion SpecificationAttributes #endregion productWatchesCertinaDSPodiumBigSize @@ -13611,14 +12400,14 @@ public IList Products() var manuSony = _ctx.Set().First(c => c.Name == "Sony"); var manuEASports = _ctx.Set().First(c => c.Name == "EA Sports"); var manuUbisoft = _ctx.Set().First(c => c.Name == "Ubisoft"); - var categoryGaming = this._ctx.Set().First(c => c.Alias == "Gaming"); - var categoryGamingAccessories = this._ctx.Set().First(c => c.Alias == "Gaming Accessories"); - var categoryGamingGames = this._ctx.Set().First(c => c.Alias == "Games"); + var categoryGaming = _ctx.Set().First(c => c.Alias == "Gaming"); + var categoryGamingAccessories = _ctx.Set().First(c => c.Alias == "Gaming Accessories"); + var categoryGamingGames = _ctx.Set().First(c => c.Alias == "Games"); var manuWarnerHomme = _ctx.Set().First(c => c.Name == "Warner Home Video Games"); #region bundlePs3AssassinCreed - var productPs3 = new Product() + var productPs3 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13640,25 +12429,16 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; - productPs3.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 4 }); - - productPs3.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_w_controller.jpg"), "image/png", GetSeName(productPs3.Name) + "-controller"), - DisplayOrder = 1 - }); - productPs3.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ps4_wo_controller.jpg"), "image/jpeg", GetSeName(productPs3.Name) + "-single"), - DisplayOrder = 2 - }); + AddProductPicture(productPs3, "product_ps4_w_controller.jpg", "ps4-w-controller"); + AddProductPicture(productPs3, "product_ps4_wo_controller.jpg", "ps4-wo-single"); + productPs3.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs3.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 4 }); - var productDualshock4Controller = new Product() + var productDualshock4Controller = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13679,20 +12459,15 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productDualshock4Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 1 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - productDualshock4Controller.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_dualshock4.jpg"), "image/png", GetSeName(productDualshock4Controller.Name)), - DisplayOrder = 1 - }); + AddProductPicture(productDualshock4Controller, "product_dualshock4.jpg"); + productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productDualshock4Controller.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 1 }); - var productMinecraft = new Product() + var productMinecraft = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13715,20 +12490,15 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productMinecraft.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productMinecraft.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 4 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - productMinecraft.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_minecraft.jpg"), "image/jpeg", GetSeName("Minecraft - Playstation 4 Edition")), - DisplayOrder = 1 - }); + AddProductPicture(productMinecraft, "product_minecraft.jpg"); + productMinecraft.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productMinecraft.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 4 }); - var productBundlePs3AssassinCreed = new Product() + var productBundlePs3AssassinCreed = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -13749,27 +12519,22 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - ShowOnHomePage = true, + DeliveryTimeId = firstDeliveryTime.Id, + ShowOnHomePage = true, BundleTitleText = "Bundle includes", BundlePerItemPricing = true, BundlePerItemShoppingCart = true }; - productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 1 }); - - productBundlePs3AssassinCreed.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ps4_bundle_minecraft.jpg"), "image/png", GetSeName(productBundlePs3AssassinCreed.Name)), - DisplayOrder = 1 - }); + AddProductPicture(productBundlePs3AssassinCreed, "ps4_bundle_minecraft.jpg"); + productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 1 }); #endregion bundlePs3AssassinCreed #region bundlePs4 - var productPs4 = new Product() + var productPs4 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13791,58 +12556,17 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; - productPs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 5 }); + AddProductPicture(productPs4, "product_sony_ps4.png", "sony-ps4"); + AddProductPicture(productPs4, "product_sony_dualshock4_wirelesscontroller.png", "sony-ps4-dualshock"); - productPs4.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4.png"), "image/png", GetSeName(productPs4.Name)), - DisplayOrder = 1 - }); - productPs4.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productPs4.Name)), - DisplayOrder = 1 - }); + productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 5 }); - //var productDualshock4Controller = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Sony-PS410037", - // Name = "DUALSHOCK 4 Wireless Controller", - // ShortDescription = "Combining classic controls with innovative new ways to play, the DUALSHOCK®4 wireless controller is an evolutionary controller for a new era of gaming.", - // FullDescription = "

Keys / Switches : PS button, SHARE button, OPTIONS button, Directional buttons (Up/Down/Left/Right), Action buttons (Triangle, Circle, Cross, Square), R1/L1/R2/L2/R3/L3, Right stick, Left stick, Touch Pad Button. The DualShock 4 is currently available in Jet Black, Magma Red, and Wave Blue.

The DualShock 4 features the following buttons: PS button, SHARE button, OPTIONS button, directional buttons, action buttons (triangle, circle, cross, square), shoulder buttons (R1/L1), triggers (R2/L2), analog stick click buttons (L3/R3) and a touch pad click button.[25] These mark several changes from the DualShock 3 and other previous PlayStation controllers. The START and SELECT buttons have been merged into a single OPTIONS button.[25][27] A dedicated SHARE button will allow players to upload video from their gameplay experiences.[25] The joysticks and triggers have been redesigned based on developer input.[25] with the ridged surface of the joysticks now featuring an outer ring surrounding the concave dome caps.

The DualShock 4 is backward compatible with the PlayStation 3, but only via a microUSB cable. Backward compatibility is not supported via Bluetooth.

", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "DUALSHOCK 4 Wireless Controller", - // Price = 59.99M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productDualshock4Controller.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - //productDualshock4Controller.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 2 }); - - //productDualshock4Controller.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productDualshock4Controller.Name)), - // DisplayOrder = 1 - //}); - - - var productPs4Camera = new Product() + var productPs4Camera = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13862,20 +12586,15 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productPs4Camera.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4Camera.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 3 }); + DeliveryTimeId = firstDeliveryTime.Id + }; - productPs4Camera.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), - DisplayOrder = 1 - }); + AddProductPicture(productPs4Camera, "product_sony_ps4_camera.png"); + productPs4Camera.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productPs4Camera.ProductCategories.Add(new ProductCategory { Category = categoryGamingAccessories, DisplayOrder = 3 }); - var productBundlePs4 = new Product() + var productBundlePs4 = new Product { ProductType = ProductType.BundledProduct, VisibleIndividually = true, @@ -13897,24 +12616,19 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - BundleTitleText = "Bundle includes" + DeliveryTimeId = firstDeliveryTime.Id, + BundleTitleText = "Bundle includes" }; - productBundlePs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productBundlePs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 2 }); - - productBundlePs4.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_bundle.png"), "image/png", GetSeName(productBundlePs4.Name)), - DisplayOrder = 1 - }); + AddProductPicture(productBundlePs4, "product_sony_ps4_bundle.png"); + productBundlePs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productBundlePs4.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 2 }); #endregion bundlePs4 #region groupAccessories - var productGroupAccessories = new Product() + var productGroupAccessories = new Product { ProductType = ProductType.GroupedProduct, VisibleIndividually = true, @@ -13937,52 +12651,15 @@ public IList Products() ShowOnHomePage = true }; - productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productGroupAccessories.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 3 }); - - productGroupAccessories.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), - DisplayOrder = 1 - }); + AddProductPicture(productGroupAccessories, "category_gaming_accessories.png"); + productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productGroupAccessories.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 3 }); #endregion groupAccessories #region Ps3PlusOneGame - //var productWatchDogs = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Ubi-watchdogs", - // Name = "Watch Dogs", - // ShortDescription = "Hack and control the city – Use the city systems as weapons: traffic lights, security cameras, movable bridges, gas pipes, electricity grid and more.", - // FullDescription = "

In today's hyper-connected world, Chicago has the country’s most advanced computer system – one which controls almost every piece of city technology and holds key information on all of the city's residents.

You play as Aiden Pearce, a brilliant hacker but also a former thug, whose criminal past lead to a violent family tragedy. Now on the hunt for those who hurt your family, you'll be able to monitor and hack all who surround you while manipulating the city's systems to stop traffic lights, download personal information, turn off the electrical grid and more.

Use the city of Chicago as your ultimate weapon and exact your own style of revenge.

Monitor the masses – Everyone leaves a digital shadow - access all data on anyone and use it to your advantage.

State of the art graphics

", - // ProductTemplateId = productTemplateSimple.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "Watch Dogs", - // Price = 49.90M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 10000, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productWatchDogs.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - //productWatchDogs.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 1 }); - - //productWatchDogs.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-watchdogs.jpg"), "image/jpeg", GetSeName(productWatchDogs.Name)), - // DisplayOrder = 1 - //}); - - var productPrinceOfPersia = new Product() + var productPrinceOfPersia = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -14002,21 +12679,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; - productPrinceOfPersia.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - productPrinceOfPersia.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + AddProductPicture(productPrinceOfPersia, "products_princeofpersia.jpg"); + productPrinceOfPersia.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuUbisoft, DisplayOrder = 1 }); + productPrinceOfPersia.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productPrinceOfPersia.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_princeofpersia.jpg"), "image/jpeg", GetSeName(productPrinceOfPersia.Name)), - DisplayOrder = 1 - }); #endregion Ps3PlusOneGame #region Horizon Zero Down - var productHorizonZeroDown = new Product() + + var productHorizonZeroDown = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -14038,21 +12712,18 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; - productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productHorizonZeroDown.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + AddProductPicture(productHorizonZeroDown, "product_horizon.jpg"); + productHorizonZeroDown.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuSony, DisplayOrder = 1 }); + productHorizonZeroDown.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productHorizonZeroDown.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_horizon.jpg"), "image/jpeg", GetSeName(productHorizonZeroDown.Name)), - DisplayOrder = 1 - }); #endregion Horizon Zero Down #region Fifa 17 - var productFifa17 = new Product() + + var productFifa17 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -14074,26 +12745,21 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime + DeliveryTimeId = firstDeliveryTime.Id }; - productFifa17.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuEASports, DisplayOrder = 1 }); - productFifa17.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 2 }); + AddProductPicture(productFifa17, "product_fifa17.jpg"); + productFifa17.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuEASports, DisplayOrder = 1 }); + productFifa17.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 2 }); - productFifa17.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_fifa17.jpg"), "image/jpeg", GetSeName(productFifa17.Name)), - DisplayOrder = 1 - }); #endregion Fifa 17 #region Lego Worlds - //productDriverSanFrancisco - var productLegoWorlds = new Product() + + var productLegoWorlds = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - //Sku = "Ubi-driversanfrancisco", Sku = "Gaming-Lego-001", Name = "LEGO Worlds - PlayStation 4", ShortDescription = "Experience a galaxy of Worlds made entirely from LEGO bricks.", @@ -14111,70 +12777,32 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + DeliveryTimeId = firstDeliveryTime.Id + }; - productLegoWorlds.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuWarnerHomme, DisplayOrder = 1 }); - productLegoWorlds.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 3 }); + AddProductPicture(productLegoWorlds, "product_legoworlds.jpg"); + productLegoWorlds.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuWarnerHomme, DisplayOrder = 1 }); + productLegoWorlds.ProductCategories.Add(new ProductCategory { Category = categoryGamingGames, DisplayOrder = 3 }); - productLegoWorlds.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_legoworlds.jpg"), "image/jpeg", GetSeName(productLegoWorlds.Name)), - DisplayOrder = 1 - }); #endregion Lego Worlds - #region Ps3PlusOneGame - //var productPs3OneGame = new Product() - //{ - // ProductType = ProductType.SimpleProduct, - // VisibleIndividually = true, - // Sku = "Sony-PS310111", - // Name = "PlayStation 3 plus game cheaper", - // ShortDescription = "Our special offer: PlayStation 3 plus one game of your choise cheaper.", - // FullDescription = productPs3.FullDescription, - // ProductTemplateId = productTemplate.Id, - // AllowCustomerReviews = true, - // Published = true, - // MetaTitle = "PlayStation 3 plus game cheaper", - // Price = 160.00M, - // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 3, - // StockQuantity = 10000, - // NotifyAdminForQuantityBelow = 1, - // AllowBackInStockSubscriptions = false, - // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime - //}; - - //productPs3OneGame.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - //productPs3OneGame.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 6 }); - - //productPs3OneGame.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps3_plus_game.png"), "image/png", GetSeName(productPs3OneGame.Name)), - // DisplayOrder = 1 - //}); - - #endregion Ps3PlusOneGame - - #endregion gaming var entities = new List { - productTRANSOCEANCHRONOGRAPH,productTissotTTouchExpertSolar,productSeikoSRPA49K1,productTitleistSM6TourChrome,productTitleistProV1x,productGBBEpicSubZeroDriver,productSupremeGolfball,productBooksStoneOfTheWise,productNikeStrikeFootball,productNikeEvoPowerBall, - productTorfabrikOfficialGameBall,productAdidasTangoSalaBall,productAllCourtBasketball,productEvolutionHighSchoolGameBasketball,productRayBanTopBar, - productOriginalWayfarer,productCustomFlakSunglasses,productRadarEVPrizmSportsSunglasses,productAppleProHipsterBundle,product97ipad,productAirpods, + productTRANSOCEANCHRONOGRAPH, productTissotTTouchExpertSolar, productSeikoSRPA49K1, + productTitleistSM6TourChrome, productTitleistProV1x, productGBBEpicSubZeroDriver, + productSupremeGolfball, productBooksStoneOfTheWise, productNikeStrikeFootball, productNikeEvoPowerBall, + productTorfabrikOfficialGameBall, productAdidasTangoSalaBall, productAllCourtBasketball, productEvolutionHighSchoolGameBasketball, productRayBanTopBar, + productOriginalWayfarer, productCustomFlakSunglasses, productRadarEVPrizmSportsSunglasses, productAppleProHipsterBundle, product97ipad, productAirpods, productIphoneplus,productWatchSeries2,product10GiftCard, product25GiftCard, product50GiftCard,product100GiftCard, productBooksUberMan, productBooksGefangeneDesHimmels, - productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, productBooksAutoBuch, productBooksFastCars, - productBooksMotorradAbenteuer, - productInstantDownloadVivaldi, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, + productBooksBestGrillingRecipes, productBooksCookingForTwo, productBooksAutosDerSuperlative, productBooksBildatlasMotorraeder, + productBooksAutoBuch, productBooksFastCars, productBooksMotorradAbenteuer, + productInstantDownloadVivaldi, productInstantDownloadBeethoven, productWatchesCertinaDSPodiumBigSize, productPs3, productMinecraft, productBundlePs3AssassinCreed, productPs4, productDualshock4Controller, productPs4Camera, productBundlePs4, productGroupAccessories, - productPrinceOfPersia, productLegoWorlds,productHorizonZeroDown,productFifa17 + productPrinceOfPersia, productLegoWorlds, productHorizonZeroDown, productFifa17 }; entities.AddRange(GetFashionProducts()); @@ -14321,7 +12949,76 @@ public IList ProductBundleItems() return entities; } - public void AssignGroupedProducts(IList savedProducts) + public void AddDownloads(IList savedProducts) + { + // Sample downloads. + var sampleDownloadSkus = new List { "P-1017", "P-1016", "P-6001" }; + var sampleDownloadProducts = savedProducts + .Where(x => sampleDownloadSkus.Contains(x.Sku)) + .ToDictionary(x => x.Sku); + + foreach (var product in sampleDownloadProducts.Values) + { + if (product.Sku.IsCaseInsensitiveEqual("P-1017")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "audio/mp3", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "beethoven-fur-elise.mp3") + }, + Extension = ".mp3", + Filename = "beethoven-fur-elise.mp3", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + else if (product.Sku.IsCaseInsensitiveEqual("P-1016")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "audio/mp3", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "vivaldi-four-seasons-spring.mp3") + }, + Extension = ".mp3", + Filename = "vivaldi-four-seasons-spring", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + else if (product.Sku.IsCaseInsensitiveEqual("P-6001")) + { + product.SampleDownload = new Download + { + EntityId = product.Id, + EntityName = nameof(Product), + DownloadGuid = Guid.NewGuid(), + ContentType = "application/pdf", + MediaStorage = new MediaStorage + { + Data = File.ReadAllBytes(_sampleDownloadsPath + "Stone_of_the_wise_preview.pdf") + }, + Extension = ".pdf", + Filename = "Stone_of_the_wise_preview", + IsNew = true, + UpdatedOnUtc = DateTime.UtcNow + }; + } + } + + _ctx.SaveChanges(); + } + + public void AssignGroupedProducts(IList savedProducts) { int productGamingAccessoriesId = savedProducts.First(x => x.Sku == "Sony-GroupAccessories").Id; var gamingAccessoriesSkus = new List() { "Sony-PS399004", "PD-Minecraft4ps4", "Sony-PS410037", "Sony-PS410040" }; @@ -14337,10 +13034,9 @@ public void AssignGroupedProducts(IList savedProducts) //_ctx.Entry(x).State = System.Data.Entity.EntityState.Modified; }); - _ctx.SaveChanges(); + _ctx.SaveChanges(); } - #region ForumGroups public IList ForumGroups() { var forumGroupGeneral = new ForumGroup @@ -14358,9 +13054,7 @@ public IList ForumGroups() this.Alter(entities); return entities; } - #endregion ForumGroups - #region Forums public IList Forums() { var newProductsForum = new Forum @@ -14395,9 +13089,6 @@ public IList Forums() this.Alter(entities); return entities; } - #endregion Forums - - #region Discounts public IList Discounts() { @@ -14433,9 +13124,6 @@ public IList Discounts() return entities; } - #endregion Discounts - - #region Deliverytimes public IList DeliveryTimes() { var entities = new List() @@ -14463,10 +13151,6 @@ public IList DeliveryTimes() return entities; } - #endregion Deliverytimes - - #region QuantityUnits - public IList QuantityUnits() { var entities = new List() @@ -14501,9 +13185,6 @@ public IList QuantityUnits() return entities; } - #endregion - - #region BlogPost public IList BlogPosts() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14534,9 +13215,7 @@ public IList BlogPosts() this.Alter(entities); return entities; } - #endregion BlogPost - #region NewsItems public IList NewsItems() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14571,9 +13250,7 @@ public IList NewsItems() this.Alter(entities); return entities; } - #endregion NewsItems - #region PollAnswer public IList PollAnswers() { var pollAnswer1 = new PollAnswer() @@ -14625,9 +13302,7 @@ public IList PollAnswers() this.Alter(entities); return entities; } - #endregion PollAnswer - #region Polls public IList Polls() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14717,8 +13392,6 @@ public IList Polls() this.Alter(entities); return entities; } - #endregion Polls - #region Alterations @@ -14906,6 +13579,10 @@ protected virtual void Alter(IList entities) { } + protected virtual void Alter(UrlRecord entity) + { + } + #endregion Alterations #endregion Sample data creators @@ -14936,6 +13613,53 @@ protected string SampleDownloadsPath } } + public virtual UrlRecord CreateUrlRecordFor(T entity) where T : BaseEntity, ISlugSupported, new() + { + var name = ""; + var languageId = 0; + + switch (entity) + { + case Category x: + name = x.Name; + break; + case Manufacturer x: + name = x.Name; + break; + case Product x: + name = x.Name; + break; + case BlogPost x: + name = x.Title; + languageId = x.LanguageId; + break; + case NewsItem x: + name = x.Title; + languageId = x.LanguageId; + break; + case Topic x: + name = SeoHelper.GetSeName(x.SystemName, true, false).Truncate(400); + break; + } + + if (name.HasValue()) + { + var result = new UrlRecord + { + EntityId = entity.Id, + EntityName = entity.GetUnproxiedType().Name, + LanguageId = languageId, + Slug = name, + IsActive = true + }; + + this.Alter(result); + return result; + } + + return null; + } + protected Picture CreatePicture(byte[] pictureBinary, string mimeType, string seoFilename) { mimeType = mimeType.EmptyNull().Truncate(20); @@ -14954,7 +13678,30 @@ protected Picture CreatePicture(byte[] pictureBinary, string mimeType, string se return picture; } - protected string GetSeName(string name) + protected void AddProductPicture( + Product product, + string imageName, + string seName = null, + int displayOrder = 1) + { + string mimeType = null; + if (imageName.EndsWith(".png")) + { + mimeType = "image/png"; + } + else if (imageName.EndsWith(".gif")) + { + mimeType = "image/gif"; + } + + product.ProductPictures.Add(new ProductPicture + { + Picture = CreatePicture(File.ReadAllBytes(_sampleImagesPath + imageName), mimeType ?? "image/jpeg", seName ?? GetSeName(product.Name)), + DisplayOrder = displayOrder + }); + } + + protected string GetSeName(string name) { return SeoHelper.GetSeName(name, true, false); } @@ -14968,14 +13715,16 @@ protected Currency CreateCurrency(string locale, decimal rate = 1M, string forma info = new RegionInfo(locale); if (info != null) { - currency = new Currency(); - currency.DisplayLocale = locale; - currency.Name = info.CurrencyNativeName; - currency.CurrencyCode = info.ISOCurrencySymbol; - currency.Rate = rate; - currency.CustomFormatting = formatting; - currency.Published = published; - currency.DisplayOrder = order; + currency = new Currency + { + DisplayLocale = locale, + Name = info.CurrencyNativeName, + CurrencyCode = info.ISOCurrencySymbol, + Rate = rate, + CustomFormatting = formatting, + Published = published, + DisplayOrder = order + }; } } catch diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 3af5d18d3d..5f527da1fb 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -33,6 +33,7 @@ prompt 4 false + latest pdbonly @@ -42,6 +43,7 @@ prompt 4 false + latest true @@ -51,6 +53,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest true @@ -60,6 +63,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest @@ -137,6 +141,7 @@ + @@ -145,6 +150,7 @@ + 201403112331027_Initial.cs @@ -557,6 +563,66 @@ 201802081830029_ShippingMethodMultistore.cs + + + 201802270844034_ExportAttributeMappings.cs + + + + 201804060721031_Wallet.cs + + + + 201804090744324_ForceSslForAllPages.cs + + + + 201804200835273_V310Resources.cs + + + + 201804252356096_TopicSlugs.cs + + + + 201805250724399_V315Resources.cs + + + + 201806051221399_RefundReturnRequests.cs + + + + 201806231547270_ScheduleTaskHistory.cs + + + + 201807051830375_MoveCustomerFields.cs + + + + 201807122120062_TopicAcl.cs + + + + 201807191020207_OrderItemDeliveryTime.cs + + + + 201807201157391_DownloadVersions.cs + + + + 201807311708428_ProductPreviewPicture.cs + + + + 201808051818238_Merge4.cs + + + + 201809171309522_NewsletterSubscriptionLanguage.cs + @@ -1009,6 +1075,51 @@ 201802081830029_ShippingMethodMultistore.cs + + 201802270844034_ExportAttributeMappings.cs + + + 201804060721031_Wallet.cs + + + 201804090744324_ForceSslForAllPages.cs + + + 201804200835273_V310Resources.cs + + + 201804252356096_TopicSlugs.cs + + + 201805250724399_V315Resources.cs + + + 201806051221399_RefundReturnRequests.cs + + + 201806231547270_ScheduleTaskHistory.cs + + + 201807051830375_MoveCustomerFields.cs + + + 201807122120062_TopicAcl.cs + + + 201807191020207_OrderItemDeliveryTime.cs + + + 201807201157391_DownloadVersions.cs + + + 201807311708428_ProductPreviewPicture.cs + + + 201808051818238_Merge4.cs + + + 201809171309522_NewsletterSubscriptionLanguage.cs + diff --git a/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs index 3cb6a0781c..c5a120e581 100644 --- a/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs +++ b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs @@ -12,21 +12,81 @@ using SmartStore.Utilities; using System.IO; using System.Xml.Linq; +using SmartStore.Core.Domain.Configuration; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.IO; +using System.Text.RegularExpressions; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Media; +using System.Data.Entity.Migrations; namespace SmartStore.Data.Utilities { public static class DataMigrator { - #region Product.MainPicture - - /// - /// Fixes 'MainPictureId' property of a single product entity - /// - /// Database context (must be ) - /// When null, Product.ProductPictures gets called. - /// Product to fix - /// true when value was fixed - public static bool FixProductMainPictureId(IDbContext context, Product product, IEnumerable entities = null) + #region Download.ProductId + + /// + /// Sets EntityId & EntityName for download table + /// + /// + /// + public static int SetDownloadProductId(IDbContext context) + { + var ctx = context as SmartObjectContext; + if (ctx == null) + throw new ArgumentException("Passed context must be an instance of type '{0}'.".FormatInvariant(typeof(SmartObjectContext)), nameof(context)); + +#pragma warning disable 612, 618 + // Get all products with a download + var productQuery = from p in ctx.Set().AsNoTracking() + where (p.DownloadId != 0) + orderby p.Id + select new { p.Id, p.DownloadId }; +#pragma warning restore 612, 618 + + var downloads = context.Set().Select(x => x).ToDictionary(x => x.Id); + + int pageIndex = -1; + while (true) + { + var products = PagedList.Create(productQuery, ++pageIndex, 100); + + foreach (var p in products) + { + try + { + if (downloads.TryGetValue(p.DownloadId, out var download)) + { + download.EntityId = p.Id; + download.EntityName = "Product"; + } + } + catch { } + } + + context.SaveChanges(); + + if (!products.HasNextPage) + break; + } + + return 0; + } + + #endregion + + #region Product.MainPicture + + /// + /// Fixes 'MainPictureId' property of a single product entity + /// + /// Database context (must be ) + /// When null, Product.ProductPictures gets called. + /// Product to fix + /// true when value was fixed + public static bool FixProductMainPictureId(IDbContext context, Product product, IEnumerable entities = null) { Guard.NotNull(product, nameof(product)); @@ -73,7 +133,7 @@ public static bool FixProductMainPictureId(IDbContext context, Product product, /// The total count of fixed and updated product entities public static int FixProductMainPictureIds(IDbContext context, DateTime? ifModifiedSinceUtc = null) { - return FixProductMainPictureIds(context, false); + return FixProductMainPictureIds(context, false, ifModifiedSinceUtc); } /// @@ -149,7 +209,7 @@ group pp by pp.ProductId into g select new { ProductId = g.Key, - PictureIds = g.OrderBy(x => x.DisplayOrder) + PictureIds = g.OrderBy(x => x.DisplayOrder).ThenBy(x => x.Id) .Take(1) .Select(x => x.PictureId) }; @@ -161,6 +221,79 @@ group pp by pp.ProductId into g #endregion + #region MoveFsMedia (V3.1) + + /// + /// Reorganizes media files in subfolders for V3.1 + /// + /// + /// + public static int MoveFsMedia(IDbContext context) + { + var ctx = context as SmartObjectContext; + if (ctx == null) + throw new ArgumentException("Passed context must be an instance of type '{0}'.".FormatInvariant(typeof(SmartObjectContext)), nameof(context)); + + int dirMaxLength = 4; + + // Check whether FS storage provider is active... + var setting = context.Set().FirstOrDefault(x => x.Name == "Media.Storage.Provider"); + if (setting == null || !setting.Value.IsCaseInsensitiveEqual("MediaStorage.SmartStoreFileSystem")) + { + // DB provider is active: no need to move anything. + return 0; + } + + // What a huge, fucking hack! > IMediaFileSystem is defined in an + // assembly which we don't reference from here. But it also implements + // IFileSystem, which we can cast to. + var fsType = Type.GetType("SmartStore.Services.Media.IMediaFileSystem, SmartStore.Services"); + var fs = EngineContext.Current.Resolve(fsType) as IFileSystem; + + // Pattern for file matching. E.g. matches 0000234-0.png + var rg = new Regex(@"^([0-9]{7})-0[.](.{3,4})$", RegexOptions.Compiled | RegexOptions.Singleline); + var subfolders = new Dictionary(); + int i = 0; + + // Get root files + var files = fs.ListFiles(""); + foreach (var chunk in files.Slice(500)) + { + foreach (var file in chunk) + { + var match = rg.Match(file.Name); + if (match.Success) + { + var name = match.Groups[1].Value; + var ext = match.Groups[2].Value; + // The new file name without trailing -0 + var newName = string.Concat(name, ".", ext); + // The subfolder name, e.g. 0024, when file name is 0024893.png + var dirName = name.Substring(0, dirMaxLength); + + if (!subfolders.TryGetValue(dirName, out string subfolder)) + { + // Create subfolder "Storage/0000" + subfolder = fs.Combine("Storage", dirName); + fs.TryCreateFolder(subfolder); + subfolders[dirName] = subfolder; + } + + // Build destination path + var destinationPath = fs.Combine(subfolder, newName); + + // Move the file now! + fs.RenameFile(file.Path, destinationPath); + i++; + } + } + } + + return i; + } + + #endregion + #region Address Formats public static int ImportAddressFormats(IDbContext context) @@ -198,5 +331,113 @@ public static int ImportAddressFormats(IDbContext context) } #endregion + + #region MoveCustomerFields (V3.2) + + /// + /// Moves several customer fields saved as generic attributes to customer entity (Title, FirstName, LastName, BirthDate, Company, CustomerNumber) + /// + /// Database context (must be ) + /// The total count of fixed and updated customer entities + public static int MoveCustomerFields(IDbContext context) + { + var ctx = context as SmartObjectContext; + if (ctx == null) + throw new ArgumentException("Passed context must be an instance of type '{0}'.".FormatInvariant(typeof(SmartObjectContext)), nameof(context)); + + // We delete attrs only if the WHOLE migration succeeded + var attrIdsToDelete = new List(1000); + var gaTable = context.Set(); + var candidates = new[] { "Title", "FirstName", "LastName", "Company", "CustomerNumber", "DateOfBirth" }; + + var query = gaTable + .AsNoTracking() + .Where(x => x.KeyGroup == "Customer" && candidates.Contains(x.Key)) + .OrderBy(x => x.Id); + + int numUpdated = 0; + + using (var scope = new DbContextScope(ctx: context, validateOnSave: false, hooksEnabled: false, autoCommit: false)) + { + for (var pageIndex = 0; pageIndex < 9999999; ++pageIndex) + { + var attrs = new PagedList(query, pageIndex, 250); + + var customerIds = attrs.Select(a => a.EntityId).Distinct().ToArray(); + var customers = context.Set() + .Where(x => customerIds.Contains(x.Id)) + .ToDictionary(x => x.Id); + + // Move attrs one by one to customer + foreach (var attr in attrs) + { + var customer = customers.Get(attr.EntityId); + if (customer == null) + continue; + + switch (attr.Key) + { + case "Title": + customer.Title = attr.Value?.Truncate(100); + break; + case "FirstName": + customer.FirstName = attr.Value?.Truncate(225); + break; + case "LastName": + customer.LastName = attr.Value?.Truncate(225); + break; + case "Company": + customer.Company = attr.Value?.Truncate(255); + break; + case "CustomerNumber": + customer.CustomerNumber = attr.Value?.Truncate(100); + break; + case "DateOfBirth": + customer.BirthDate = attr.Value?.Convert(); + break; + } + + // Update FullName + var parts = new[] { customer.Title, customer.FirstName, customer.LastName }; + customer.FullName = string.Join(" ", parts.Where(x => x.HasValue())).NullEmpty(); + + attrIdsToDelete.Add(attr.Id); + } + + // Save batch + numUpdated += scope.Commit(); + + // Breathe + context.DetachAll(); + + if (!attrs.HasNextPage) + break; + } + + // Everything worked out, now delete all orpahned attributes + if (attrIdsToDelete.Count > 0) + { + try + { + // Don't rollback migration when this fails + var stubs = attrIdsToDelete.Select(x => new GenericAttribute { Id = x }).ToList(); + foreach (var chunk in stubs.Slice(500)) + { + chunk.Each(x => gaTable.Attach(x)); + gaTable.RemoveRange(chunk); + scope.Commit(); + } + } + catch (Exception ex) + { + var msg = ex.Message; + } + } + } + + return numUpdated; + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Data/Utilities/MessageTemplateConverter.cs b/src/Libraries/SmartStore.Data/Utilities/MessageTemplateConverter.cs index 40071eced8..310aa4a220 100644 --- a/src/Libraries/SmartStore.Data/Utilities/MessageTemplateConverter.cs +++ b/src/Libraries/SmartStore.Data/Utilities/MessageTemplateConverter.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.IO; using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; @@ -33,13 +31,14 @@ public MessageTemplateConverter(IDbContext context) /// /// Name of template without extension, e.g. 'GiftCard.Notification' /// Language + /// The virtual root path of templates to load, e.g. "~/Plugins/MyPlugins/EmailTemplates". Default is "~/App_Data/EmailTemplates". /// Deserialized template xml - public MessageTemplate Load(string templateName, Language language) + public MessageTemplate Load(string templateName, Language language, string virtualRootPath = null) { Guard.NotEmpty(templateName, nameof(templateName)); Guard.NotNull(language, nameof(language)); - var dir = ResolveTemplateDirectory(language); + var dir = ResolveTemplateDirectory(language, virtualRootPath); var fullPath = Path.Combine(dir.FullName, templateName + ".xml"); if (!File.Exists(fullPath)) @@ -54,12 +53,13 @@ public MessageTemplate Load(string templateName, Language language) /// Loads all message templates from disk (~/App_Data/EmailTemplates/) /// /// Language + /// The virtual root path of templates to load, e.g. "~/Plugins/MyPlugins/EmailTemplates". Default is "~/App_Data/EmailTemplates". /// List of deserialized template xml - public IEnumerable LoadAll(Language language) + public IEnumerable LoadAll(Language language, string virtualRootPath = null) { Guard.NotNull(language, nameof(language)); - var dir = ResolveTemplateDirectory(language); + var dir = ResolveTemplateDirectory(language, virtualRootPath); var files = dir.EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly); foreach (var file in files) @@ -114,28 +114,32 @@ public XmlDocument Save(MessageTemplate template, Language language) /// /// Imports all template xml files to MessageTemplate table /// - public void ImportAll(Language language) + /// The virtual root path of templates to import, e.g. "~/Plugins/MyPlugins/EmailTemplates". Default is "~/App_Data/EmailTemplates". + public void ImportAll(Language language, string virtualRootPath = null) { var table = _ctx.Set(); - var sourceTemplates = LoadAll(language); + var sourceTemplates = LoadAll(language, virtualRootPath); var dbTemplatesMap = table .ToList() - .ToDictionarySafe(x => x.Name, StringComparer.OrdinalIgnoreCase); + .ToMultimap(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase); foreach (var source in sourceTemplates) { - if (dbTemplatesMap.TryGetValue(source.Name, out var target)) + if (dbTemplatesMap.ContainsKey(source.Name)) { - if (source.To.HasValue()) target.To = source.To; - if (source.ReplyTo.HasValue()) target.ReplyTo = source.ReplyTo; - if (source.Subject.HasValue()) target.Subject = source.Subject; - if (source.ModelTypes.HasValue()) target.ModelTypes = source.ModelTypes; - if (source.Body.HasValue()) target.Body = source.Body; + foreach (var target in dbTemplatesMap[source.Name]) + { + if (source.To.HasValue()) target.To = source.To; + if (source.ReplyTo.HasValue()) target.ReplyTo = source.ReplyTo; + if (source.Subject.HasValue()) target.Subject = source.Subject; + if (source.ModelTypes.HasValue()) target.ModelTypes = source.ModelTypes; + if (source.Body.HasValue()) target.Body = source.Body; + } } else { - target = new MessageTemplate + var template = new MessageTemplate { Name = source.Name, To = source.To, @@ -147,17 +151,17 @@ public void ImportAll(Language language) EmailAccountId = (_defaultEmailAccount?.Id).GetValueOrDefault(), }; - table.Add(target); + table.Add(template); } } _ctx.SaveChanges(); } - private DirectoryInfo ResolveTemplateDirectory(Language language) + private DirectoryInfo ResolveTemplateDirectory(Language language, string virtualRootPath = null) { - var rootPath = CommonHelper.MapPath("~/App_Data/EmailTemplates/"); - var testPaths = new[] + var rootPath = CommonHelper.MapPath(virtualRootPath.NullEmpty() ?? "~/App_Data/EmailTemplates/"); + var testPaths = new[] { language.LanguageCulture, language.GetTwoLetterISOLanguageName(), @@ -171,7 +175,7 @@ private DirectoryInfo ResolveTemplateDirectory(Language language) return new DirectoryInfo(path); } } - + throw new DirectoryNotFoundException($"Could not obtain an email templates path for language {language.LanguageCulture}. Fallback to 'en' failed, because directory does not exist."); } @@ -184,7 +188,7 @@ private MessageTemplate DeserializeDocument(XDocument doc) { var root = doc.Root; var result = new MessageTemplate(); - + foreach (var node in root.Nodes().OfType()) { var value = node.Value.Trim(); diff --git a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs index fe7fd42bfb..294603323a 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs @@ -123,21 +123,19 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame var details = new RegistrationDetails(parameters); var randomPassword = CommonHelper.GenerateRandomDigitCode(20); - bool isApproved = _customerSettings.UserRegistrationType == UserRegistrationType.Standard; var registrationRequest = new CustomerRegistrationRequest(currentCustomer, details.EmailAddress, _customerSettings.UsernamesEnabled ? details.UserName : details.EmailAddress, randomPassword, PasswordFormat.Clear, isApproved); var registrationResult = _customerRegistrationService.RegisterCustomer(registrationRequest); if (registrationResult.Success) { - //store other parameters (form fields) - if (!String.IsNullOrEmpty(details.FirstName)) - _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.FirstName, details.FirstName); + // store other parameters (form fields) + if (!String.IsNullOrEmpty(details.FirstName)) + currentCustomer.FirstName = details.FirstName; if (!String.IsNullOrEmpty(details.LastName)) - _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.LastName, details.LastName); - + currentCustomer.LastName = details.LastName; - userFound = currentCustomer; + userFound = currentCustomer; _openAuthenticationService.AssociateExternalAccountWithUser(currentCustomer, parameters); ExternalAuthorizerHelper.RemoveParameters(); @@ -209,11 +207,11 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame _openAuthenticationService.AssociateExternalAccountWithUser(userLoggedIn, parameters); } - //migrate shopping cart + // migrate shopping cart _shoppingCartService.MigrateShoppingCart(_workContext.CurrentCustomer, userFound ?? userLoggedIn); - //authenticate + // authenticate _authenticationService.SignIn(userFound ?? userLoggedIn, false); - //activity log + // activity log _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), userFound ?? userLoggedIn); diff --git a/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs b/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs index 38a945b869..80fb2b5202 100644 --- a/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/BackInStockSubscriptionService.cs @@ -71,7 +71,7 @@ public virtual IPagedList GetAllSubscriptionsByCustomer if (storeId > 0) query = query.Where(biss => biss.StoreId == storeId); //product - query = query.Where(biss => !biss.Product.Deleted); + query = query.Where(biss => !biss.Product.Deleted && !biss.Product.IsSystemProduct); query = query.OrderByDescending(biss => biss.CreatedOnUtc); return new PagedList(query, pageIndex, pageSize); diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs index f115704eed..f5ce3b7ece 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryExtensions.cs @@ -111,7 +111,7 @@ public static string GetCategoryNameIndented(this TreeNode treeNo public static string GetCategoryPath(this ICategoryNode categoryNode, ICategoryService categoryService, int? languageId = null, - bool withAlias = false, + string aliasPattern = null, string separator = " � ") { Guard.NotNull(categoryNode, nameof(categoryNode)); @@ -119,7 +119,7 @@ public static string GetCategoryPath(this ICategoryNode categoryNode, var treeNode = categoryService.GetCategoryTree(categoryNode.Id, true); if (treeNode != null) { - return categoryService.GetCategoryPath(treeNode, languageId, withAlias, separator); + return categoryService.GetCategoryPath(treeNode, languageId, aliasPattern, separator); } return string.Empty; diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 60216e9614..e280141b14 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Data.Entity; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; @@ -151,8 +152,7 @@ public virtual void InheritAclIntoChildren( } else { - AclRecord aclRecordToDelete; - if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) + if (existingAclRecords.TryGetValue(customerRole.Id, out var aclRecordToDelete)) { _aclRepository.Delete(aclRecordToDelete); } @@ -314,27 +314,7 @@ public virtual IQueryable BuildCategoriesQuery( if (alias.HasValue()) query = query.Where(c => c.Alias.Contains(alias)); - if (showHidden) - { - if (!QuerySettings.IgnoreMultiStore && storeId > 0) - { - query = from c in query - join sm in _storeMappingRepository.Table - on new { c1 = c.Id, c2 = "Category" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into c_sm - from sm in c_sm.DefaultIfEmpty() - where !c.LimitedToStores || storeId == sm.StoreId - select c; - - query = from c in query - group c by c.Id into cGroup - orderby cGroup.Key - select cGroup.FirstOrDefault(); - } - } - else - { - query = ApplyHiddenCategoriesFilter(query, storeId); - } + query = ApplyHiddenCategoriesFilter(query, storeId, showHidden); query = query.Where(c => !c.Deleted); @@ -377,52 +357,57 @@ public IList GetAllCategoriesByParentCategoryId(int parentCategoryId, if (!showHidden) query = query.Where(c => c.Published); - query = query.Where(c => c.ParentCategoryId == parentCategoryId); - query = query.Where(c => !c.Deleted); - query = query.OrderBy(c => c.DisplayOrder); + query = query.Where(c => c.ParentCategoryId == parentCategoryId && !c.Deleted); - if (!showHidden) - { - query = ApplyHiddenCategoriesFilter(query, storeId); - query = query.OrderBy(c => c.DisplayOrder); - } + query = ApplyHiddenCategoriesFilter(query, storeId, showHidden); - var categories = query.ToList(); + var categories = query.OrderBy(x => x.DisplayOrder).ToList(); return categories; }); } - protected virtual IQueryable ApplyHiddenCategoriesFilter(IQueryable query, int storeId = 0) + protected virtual IQueryable ApplyHiddenCategoriesFilter(IQueryable query, int storeId = 0, bool showHidden = false) { - // ACL (access control list) - if (!QuerySettings.IgnoreAcl) + var entityName = nameof(Category); + var applied = false; + + // Store mapping + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) { - var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - query = from c in query - join acl in _aclRepository.Table - on new { c1 = c.Id, c2 = "Category" } equals new { c1 = acl.EntityId, c2 = acl.EntityName } into c_acl - from acl in c_acl.DefaultIfEmpty() - where !c.SubjectToAcl || allowedCustomerRolesIds.Contains(acl.CustomerRoleId) + join m in _storeMappingRepository.Table + on new { c1 = c.Id, c2 = entityName } equals new { c1 = m.EntityId, c2 = m.EntityName } into cm + from m in cm.DefaultIfEmpty() + where !c.LimitedToStores || storeId == m.StoreId select c; + + applied = true; } - // Store mapping - if (!QuerySettings.IgnoreMultiStore && storeId > 0) + // ACL (access control list) + if (!showHidden && !QuerySettings.IgnoreAcl) { + var allowedCustomerRolesIds = _workContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + query = from c in query - join sm in _storeMappingRepository.Table - on new { c1 = c.Id, c2 = "Category" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into c_sm - from sm in c_sm.DefaultIfEmpty() - where !c.LimitedToStores || storeId == sm.StoreId + join a in _aclRepository.Table + on new { c1 = c.Id, c2 = entityName } equals new { c1 = a.EntityId, c2 = a.EntityName } into ca + from a in ca.DefaultIfEmpty() + where !c.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) select c; + + applied = true; + } + + if (applied) + { + // Only distinct categories (group by ID) + query = from c in query + group c by c.Id into cGroup + orderby cGroup.Key + select cGroup.FirstOrDefault(); } - // Only distinct categories (group by ID) - query = from c in query - group c by c.Id into cGroup - orderby cGroup.Key - select cGroup.FirstOrDefault(); return query; } @@ -511,7 +496,7 @@ public virtual IPagedList GetProductCategoriesByCategoryId(int { var query = from pc in _productCategoryRepository.Table join p in _productRepository.Table on pc.ProductId equals p.Id - where pc.CategoryId == categoryId && !p.Deleted && (showHidden || p.Published) + where pc.CategoryId == categoryId && !p.Deleted && !p.IsSystemProduct && (showHidden || p.Published) select pc; if (!showHidden) @@ -537,7 +522,7 @@ public virtual IList GetProductCategoriesByProductId(int produc string key = string.Format(PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY, showHidden, productId, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); return _requestCache.Get(key, () => { - var query = from pc in _productCategoryRepository.Table.Expand(x => x.Category) + var query = from pc in _productCategoryRepository.Table join c in _categoryRepository.Table on pc.CategoryId equals c.Id where pc.ProductId == productId && !c.Deleted && @@ -545,6 +530,8 @@ join c in _categoryRepository.Table on pc.CategoryId equals c.Id orderby pc.DisplayOrder select pc; + query = query.Include(x => x.Category); + var allProductCategories = query.ToList(); var result = new List(); if (!showHidden) @@ -571,12 +558,14 @@ public virtual Multimap GetProductCategoriesByProductIds(i Guard.NotNull(productIds, nameof(productIds)); var query = - from pc in _productCategoryRepository.TableUntracked.Expand(x => x.Category).Expand(x => x.Category.Picture) + from pc in _productCategoryRepository.TableUntracked join c in _categoryRepository.Table on pc.CategoryId equals c.Id where productIds.Contains(pc.ProductId) && !c.Deleted && (showHidden || c.Published) orderby pc.DisplayOrder select pc; + query = query.Include(x => x.Category.Picture); + if (hasDiscountsApplied.HasValue) { query = query.Where(x => x.Category.HasDiscountsApplied == hasDiscountsApplied); @@ -703,12 +692,12 @@ public virtual IEnumerable GetCategoryTrail(ICategoryNode node) public virtual string GetCategoryPath( TreeNode treeNode, int? languageId = null, - bool withAlias = false, + string aliasPattern = null, string separator = " � ") { Guard.NotNull(treeNode, nameof(treeNode)); - var lookupKey = "Path.{0}.{1}.{2}".FormatInvariant(separator, languageId ?? 0, withAlias); + var lookupKey = "Path.{0}.{1}.{2}".FormatInvariant(separator, languageId ?? 0, aliasPattern.HasValue()); var cachedPath = treeNode.GetMetadata(lookupKey, false); if (cachedPath != null) @@ -721,14 +710,6 @@ public virtual string GetCategoryPath( foreach (var node in trail) { - //if (!node.Value.Published) - //{ - // // If any parent is unpublished, - // // this category is not visible: so, no path. - // sb.Clear(); - // break; - //} - if (!node.IsRoot) { var cat = node.Value; @@ -739,11 +720,10 @@ public virtual string GetCategoryPath( sb.Append(name); - if (withAlias && cat.Alias.HasValue()) + if (aliasPattern.HasValue() && cat.Alias.HasValue()) { - sb.Append(" ("); - sb.Append(cat.Alias); - sb.Append(")"); + sb.Append(" "); + sb.Append(string.Format(aliasPattern, cat.Alias)); } if (node != treeNode) diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs index 22b31b1c5c..8e0741a6c8 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text; using SmartStore.Collections; using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; @@ -11,7 +10,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; -using SmartStore.Data; namespace SmartStore.Services.Catalog { @@ -43,8 +41,6 @@ public class CategoryTreeChangeHook : IDbSaveHook private readonly bool[] _handledReasons = new bool[(int)CategoryTreeChangeReason.Hierarchy + 1]; private bool _invalidated; - private static readonly HashSet _countAffectingProductProps = new HashSet(); - // Hierarchy affecting category prop names private static readonly string[] _h = new string[] { "ParentCategoryId", "Published", "Deleted", "DisplayOrder" }; // Visibility affecting category prop names @@ -64,29 +60,6 @@ public class CategoryTreeChangeHook : IDbSaveHook typeof(AclRecord) }; - static CategoryTreeChangeHook() - { - AddPropsToSet(_countAffectingProductProps, - x => x.AvailableEndDateTimeUtc, - x => x.AvailableStartDateTimeUtc, - x => x.Deleted, - x => x.LowStockActivityId, - x => x.LimitedToStores, - x => x.ManageInventoryMethodId, - x => x.MinStockQuantity, - x => x.Published, - x => x.SubjectToAcl, - x => x.VisibleIndividually); - } - - static void AddPropsToSet(HashSet props, params Expression>[] lambdas) - { - foreach (var lambda in lambdas) - { - props.Add(lambda.ExtractPropertyInfo().Name); - } - } - public CategoryTreeChangeHook(ICommonServices services, ICategoryService categoryService) { _services = services; @@ -107,7 +80,8 @@ public void OnBeforeSave(IHookedEntity entry) if (entity is Product) { var modProps = _services.DbContext.GetModifiedProperties(entity); - if (modProps.Keys.Any(x => _countAffectingProductProps.Contains(x))) + var toxicPropNames = Product.GetVisibilityAffectingPropertyNames(); + if (modProps.Keys.Any(x => toxicPropNames.Contains(x))) { // No eviction, just notification PublishEvent(CategoryTreeChangeReason.ElementCounts); @@ -122,10 +96,8 @@ public void OnBeforeSave(IHookedEntity entry) PublishEvent(CategoryTreeChangeReason.ElementCounts); } } - else if (entity is Category) + else if (entity is Category category) { - var category = entity as Category; - var modProps = _services.DbContext.GetModifiedProperties(entity); if (modProps.Keys.Any(x => _h.Contains(x))) @@ -227,6 +199,7 @@ public void OnAfterSave(IHookedEntity entry) // INFO: 'Modified' case already handled in 'OnBeforeSave()' // Hierarchy affecting change, nuke all. cache.RemoveByPattern(CategoryService.CATEGORY_TREE_PATTERN_KEY); + PublishEvent(CategoryTreeChangeReason.Hierarchy); _invalidated = true; } else if (entity is Setting) diff --git a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs index 99a7eb444f..8f096184fc 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs @@ -82,7 +82,7 @@ public virtual IList GetComparedProducts() foreach (int productId in productIds) { var product = _productService.GetProductById(productId); - if (product != null && product.Published && !product.Deleted) + if (product != null && product.Published && !product.Deleted && !product.IsSystemProduct) products.Add(product); } return products; diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index c3d587f42c..52bb1b48e5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -14,6 +14,7 @@ using SmartStore.Services.Search; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Seo; namespace SmartStore.Services.Catalog { @@ -33,6 +34,7 @@ public partial class CopyProductService : ICopyProductService private readonly IUrlRecordService _urlRecordService; private readonly IStoreMappingService _storeMappingService; private readonly ICatalogSearchService _catalogSearchService; + private readonly SeoSettings _seoSettings; public CopyProductService( IRepository productRepository, @@ -48,7 +50,8 @@ public CopyProductService( IProductAttributeParser productAttributeParser, IUrlRecordService urlRecordService, IStoreMappingService storeMappingService, - ICatalogSearchService catalogSearchService) + ICatalogSearchService catalogSearchService, + SeoSettings seoSettings) { _productRepository = productRepository; _relatedProductRepository = relatedProductRepository; @@ -64,6 +67,7 @@ public CopyProductService( _urlRecordService = urlRecordService; _storeMappingService = storeMappingService; _catalogSearchService = catalogSearchService; + _seoSettings = seoSettings; T = NullLocalizer.Instance; } @@ -87,7 +91,6 @@ public virtual Product CopyProduct( var languages = _languageService.GetAllLanguages(true); // Media stuff - int? downloadId = null; int? sampleDownloadId = null; var clonedPictures = new Dictionary(); // Key = former ID, Value = cloned picture @@ -99,15 +102,12 @@ public virtual Product CopyProduct( forceNoTracking: true, hooksEnabled: false)) { - if (product.IsDownload) - { - downloadId = CopyDownload(product.DownloadId)?.Id; - } - if (product.HasSampleDownload) { - sampleDownloadId = CopyDownload(product.SampleDownloadId.GetValueOrDefault())?.Id; - } + var sampleDownload = _downloadService.GetDownloadById((int)product.SampleDownloadId); + var sampleDownloadClone = CopyDownload(sampleDownload); + sampleDownloadId = sampleDownloadClone.Id; + } if (copyImages) { @@ -141,7 +141,6 @@ public virtual Product CopyProduct( RequiredProductIds = product.RequiredProductIds, AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts, IsDownload = product.IsDownload, - DownloadId = downloadId ?? 0, UnlimitedDownloads = product.UnlimitedDownloads, MaxNumberOfDownloads = product.MaxNumberOfDownloads, DownloadExpirationDays = product.DownloadExpirationDays, @@ -171,6 +170,8 @@ public virtual Product CopyProduct( AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions, OrderMinimumQuantity = product.OrderMinimumQuantity, OrderMaximumQuantity = product.OrderMaximumQuantity, + QuantityStep = product.QuantityStep, + QuantiyControlType = product.QuantiyControlType, HideQuantityControl = product.HideQuantityControl, AllowedQuantities = product.AllowedQuantities, DisableBuyButton = product.DisableBuyButton, @@ -196,6 +197,7 @@ public virtual Product CopyProduct( DisplayOrder = product.DisplayOrder, Published = isPublished, Deleted = product.Deleted, + IsSystemProduct = product.IsSystemProduct, DeliveryTimeId = product.DeliveryTimeId, QuantityUnitId = product.QuantityUnitId, BasePriceEnabled = product.BasePriceEnabled, @@ -344,8 +346,11 @@ public virtual Product CopyProduct( // Bundle items ProcessBundleItems(product, clone); - // >>>>>>> Our final commit - Commit(); + // Downloads + CopyDownloads(product.Id, clone.Id, "Product"); + + // >>>>>>> Our final commit + Commit(); } return clone; @@ -357,36 +362,53 @@ private void Commit() _services.DbContext.SaveChanges(); } - private Download CopyDownload(int downloadId) + private Dictionary CopyDownloads(int productId, int cloneId, string entityName = "") { - var download = _downloadService.GetDownloadById(downloadId); + var downloads = _downloadService.GetDownloadsFor(productId, entityName); + var clonedDownloads = new Dictionary(); - if (download == null) + if (!downloads.Any()) { return null; } - var clone = new Download - { - DownloadGuid = Guid.NewGuid(), - UseDownloadUrl = download.UseDownloadUrl, - DownloadUrl = download.DownloadUrl, - ContentType = download.ContentType, - Filename = download.Filename, - Extension = download.Extension, - IsNew = download.IsNew, - UpdatedOnUtc = DateTime.UtcNow - }; - - using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) - { - _downloadService.InsertDownload(clone, download.MediaStorage?.Data); + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) + { + foreach (var download in downloads) + { + download.EntityId = cloneId; + var clone = CopyDownload(download); + clonedDownloads[clone.Id] = clone; + } } - return clone; + return clonedDownloads; } - private Dictionary CopyPictures(Product product, string newProductName) + private Download CopyDownload(Download download) + { + var clone = new Download + { + DownloadGuid = Guid.NewGuid(), + UseDownloadUrl = download.UseDownloadUrl, + DownloadUrl = download.DownloadUrl, + ContentType = download.ContentType, + Filename = download.Filename, + Extension = download.Extension, + IsNew = download.IsNew, + UpdatedOnUtc = DateTime.UtcNow, + EntityId = download.EntityId, + EntityName = download.EntityName, + Changelog = download.Changelog, + FileVersion = download.FileVersion + }; + + _downloadService.InsertDownload(clone, download.MediaStorage?.Data); + + return clone; + } + + private Dictionary CopyPictures(Product product, string newProductName) { var clonedPictures = new Dictionary(); var seoFilename = _pictureService.GetPictureSeName(newProductName); @@ -423,7 +445,8 @@ private void ProcessSlug(Product clone) { using (var scope = new DbContextScope(ctx: _productRepository.Context, autoCommit: true)) { - _urlRecordService.SaveSlug(clone, clone.ValidateSeName("", clone.Name, true), 0); + var slug = clone.ValidateSeName("", clone.Name, true, _urlRecordService, _seoSettings); + _urlRecordService.SaveSlug(clone, slug, 0); } } @@ -433,36 +456,37 @@ private void ProcessLocalization(Product product, Product clone, IEnumerable x.Name, lang.Id, false, false); + var name = product.GetLocalized(x => x.Name, lang, false, false); if (!String.IsNullOrEmpty(name)) _localizedEntityService.SaveLocalizedValue(clone, x => x.Name, name, lang.Id); - var shortDescription = product.GetLocalized(x => x.ShortDescription, lang.Id, false, false); + var shortDescription = product.GetLocalized(x => x.ShortDescription, lang, false, false); if (!String.IsNullOrEmpty(shortDescription)) _localizedEntityService.SaveLocalizedValue(clone, x => x.ShortDescription, shortDescription, lang.Id); - var fullDescription = product.GetLocalized(x => x.FullDescription, lang.Id, false, false); + var fullDescription = product.GetLocalized(x => x.FullDescription, lang, false, false); if (!String.IsNullOrEmpty(fullDescription)) _localizedEntityService.SaveLocalizedValue(clone, x => x.FullDescription, fullDescription, lang.Id); - var metaKeywords = product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false); + var metaKeywords = product.GetLocalized(x => x.MetaKeywords, lang, false, false); if (!String.IsNullOrEmpty(metaKeywords)) _localizedEntityService.SaveLocalizedValue(clone, x => x.MetaKeywords, metaKeywords, lang.Id); - var metaDescription = product.GetLocalized(x => x.MetaDescription, lang.Id, false, false); + var metaDescription = product.GetLocalized(x => x.MetaDescription, lang, false, false); if (!String.IsNullOrEmpty(metaDescription)) _localizedEntityService.SaveLocalizedValue(clone, x => x.MetaDescription, metaDescription, lang.Id); - var metaTitle = product.GetLocalized(x => x.MetaTitle, lang.Id, false, false); + var metaTitle = product.GetLocalized(x => x.MetaTitle, lang, false, false); if (!String.IsNullOrEmpty(metaTitle)) _localizedEntityService.SaveLocalizedValue(clone, x => x.MetaTitle, metaTitle, lang.Id); - var bundleTitleText = product.GetLocalized(x => x.BundleTitleText, lang.Id, false, false); + var bundleTitleText = product.GetLocalized(x => x.BundleTitleText, lang, false, false); if (!String.IsNullOrEmpty(bundleTitleText)) _localizedEntityService.SaveLocalizedValue(clone, x => x.BundleTitleText, bundleTitleText, lang.Id); - // search engine name - _urlRecordService.SaveSlug(clone, clone.ValidateSeName("", name, false), lang.Id); + // Search engine name. + var slug = clone.ValidateSeName("", name, false, _urlRecordService, _seoSettings, lang.Id); + _urlRecordService.SaveSlug(clone, slug, lang.Id); } } } @@ -567,7 +591,7 @@ private void ProcessAttributes( { foreach (var lang in languages) { - var name = pvav.GetLocalized(x => x.Name, lang.Id, false, false); + var name = pvav.GetLocalized(x => x.Name, lang, false, false); if (!String.IsNullOrEmpty(name)) { var pvavClone = pvavMap.Get(pvav.Id); diff --git a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs index 711d3053b3..a415dc7665 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs @@ -194,13 +194,13 @@ IPagedList GetProductCategoriesByCategoryId( /// /// The category node /// The id of language. Pass null to skip localization. - /// true appends the category alias - if specified - to the name + /// How the category alias - if specified - should be appended to the category name (e.g. ({0})) /// The separator string /// Category breadcrumb path string GetCategoryPath( TreeNode treeNode, int? languageId = null, - bool withAlias = false, + string aliasPattern = null, string separator = " � "); /// @@ -248,7 +248,7 @@ public static string GetCategoryPath(this ICategoryService categoryService, var node = categoryService.GetCategoryTree(pc.CategoryId, false, storeId ?? 0); if (node != null) { - result = categoryService.GetCategoryPath(node, languageId, false, separator); + result = categoryService.GetCategoryPath(node, languageId, null, separator); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index 03ccc886e2..a69241071d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs @@ -43,6 +43,13 @@ public partial interface IProductAttributeService /// Product attribute void UpdateProductAttribute(ProductAttribute productAttribute); + /// + /// Gets the export mappings for a given field prefix. + /// + /// The export field prefix, e.g. gmc. + /// A multimap with export field names to ProductAttribute.Id mappings. + Multimap GetExportFieldMappings(string fieldPrefix); + #endregion #region Product attribute options diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index 739b683e7b..b585dbbc4f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -42,11 +42,18 @@ public partial interface IProductService /// Products IList GetProductsByIds(int[] productIds, ProductLoadFlags flags = ProductLoadFlags.None); - /// - /// Inserts a product - /// - /// Product - void InsertProduct(Product product); + /// + /// Get product by system name. + /// + /// System name + /// Product entity. + Product GetProductBySystemName(string systemName); + + /// + /// Inserts a product + /// + /// Product + void InsertProduct(Product product); /// /// Updates the product diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 68846923bf..d15e4ed20d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -354,8 +354,8 @@ protected virtual int ProcessProducts( row.SetProperty(context.Result, (x) => x.RequiredProductIds); // TODO: global scope row.SetProperty(context.Result, (x) => x.AutomaticallyAddRequiredProducts); row.SetProperty(context.Result, (x) => x.IsDownload); - row.SetProperty(context.Result, (x) => x.DownloadId); - row.SetProperty(context.Result, (x) => x.UnlimitedDownloads, true); + //row.SetProperty(context.Result, (x) => x.DownloadId); + //row.SetProperty(context.Result, (x) => x.UnlimitedDownloads, true); row.SetProperty(context.Result, (x) => x.MaxNumberOfDownloads, 10); row.SetProperty(context.Result, (x) => x.DownloadExpirationDays); row.SetProperty(context.Result, (x) => x.DownloadActivationTypeId, 1); @@ -384,7 +384,9 @@ protected virtual int ProcessProducts( row.SetProperty(context.Result, (x) => x.AllowBackInStockSubscriptions); row.SetProperty(context.Result, (x) => x.OrderMinimumQuantity, 1); row.SetProperty(context.Result, (x) => x.OrderMaximumQuantity, 100); - row.SetProperty(context.Result, (x) => x.HideQuantityControl); + row.SetProperty(context.Result, (x) => x.QuantityStep, 1); + row.SetProperty(context.Result, (x) => x.QuantiyControlType); + row.SetProperty(context.Result, (x) => x.HideQuantityControl); row.SetProperty(context.Result, (x) => x.AllowedQuantities); row.SetProperty(context.Result, (x) => x.DisableBuyButton); row.SetProperty(context.Result, (x) => x.DisableWishlistButton); diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index 8363f604ce..3daed078af 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Data.Entity; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; @@ -174,8 +175,8 @@ public virtual IPagedList GetProductManufacturersByManufact var query = from pm in _productManufacturerRepository.Table join p in _productRepository.Table on pm.ProductId equals p.Id where pm.ManufacturerId == manufacturerId && - !p.Deleted && - (showHidden || p.Published) + !p.Deleted && !p.IsSystemProduct && + (showHidden || p.Published) orderby pm.DisplayOrder select pm; @@ -216,15 +217,14 @@ public virtual IList GetProductManufacturersByProductId(int string key = string.Format(PRODUCTMANUFACTURERS_ALLBYPRODUCTID_KEY, showHidden, productId, _workContext.CurrentCustomer.Id, _storeContext.CurrentStore.Id); return _requestCache.Get(key, () => { - var query = from pm in _productManufacturerRepository.Table.Expand(x => x.Manufacturer.Picture) - join m in _manufacturerRepository.Table on - pm.ManufacturerId equals m.Id - where pm.ProductId == productId && - !m.Deleted && - (showHidden || m.Published) + var query = from pm in _productManufacturerRepository.Table + join m in _manufacturerRepository.Table on pm.ManufacturerId equals m.Id + where pm.ProductId == productId && !m.Deleted && (showHidden || m.Published) orderby pm.DisplayOrder select pm; + query = query.Include(x => x.Manufacturer.Picture); + if (!showHidden) { if (!QuerySettings.IgnoreMultiStore) @@ -274,11 +274,13 @@ public virtual Multimap GetProductManufacturersByProdu Guard.NotNull(productIds, nameof(productIds)); var query = - from pm in _productManufacturerRepository.TableUntracked.Expand(x => x.Manufacturer).Expand(x => x.Manufacturer.Picture) + from pm in _productManufacturerRepository.TableUntracked //join m in _manufacturerRepository.TableUntracked on pm.ManufacturerId equals m.Id // Eager loading does not work with this join where !pm.Manufacturer.Deleted && productIds.Contains(pm.ProductId) select pm; + query = query.Include(x => x.Manufacturer.Picture); + var map = query .OrderBy(x => x.ProductId) .ThenBy(x => x.DisplayOrder) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 3f6424e8a9..7c0556775b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -481,8 +481,7 @@ public virtual PriceCalculationContext CreatePriceCalculationContext( /// Product /// A value indicating whether include discounts or not for final price computation /// Final price - public virtual decimal GetFinalPrice(Product product, - bool includeDiscounts) + public virtual decimal GetFinalPrice(Product product, bool includeDiscounts) { var customer = _services.WorkContext.CurrentCustomer; return GetFinalPrice(product, customer, includeDiscounts); @@ -495,9 +494,7 @@ public virtual decimal GetFinalPrice(Product product, /// The customer /// A value indicating whether include discounts or not for final price computation /// Final price - public virtual decimal GetFinalPrice(Product product, - Customer customer, - bool includeDiscounts) + public virtual decimal GetFinalPrice(Product product, Customer customer, bool includeDiscounts) { return GetFinalPrice(product, customer, decimal.Zero, includeDiscounts); } @@ -542,7 +539,6 @@ public virtual decimal GetFinalPrice( //tier prices if (product.HasTierPrices && !bundleItem.IsValid() && includeDiscounts) { - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); Discount appliedDiscountTest = null; decimal discountAmountTest = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscountTest, bundleItem); diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceFormatter.cs b/src/Libraries/SmartStore.Services/Catalog/PriceFormatter.cs index f2780df836..5ea1ec97ed 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceFormatter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceFormatter.cs @@ -24,70 +24,12 @@ public PriceFormatter(IWorkContext workContext, ILocalizationService localizationService, TaxSettings taxSettings) { - this._workContext = workContext; - this._currencyService = currencyService; - this._localizationService = localizationService; - this._taxSettings = taxSettings; + _workContext = workContext; + _currencyService = currencyService; + _localizationService = localizationService; + _taxSettings = taxSettings; } - #region Utilities - - /// - /// Gets currency string - /// - /// Amount - /// Currency string without exchange rate - protected string GetCurrencyString(decimal amount) - { - bool showCurrency = true; - var targetCurrency = _workContext.WorkingCurrency; - return GetCurrencyString(amount, showCurrency, targetCurrency); - } - - /// - /// Gets currency string - /// - /// Amount - /// A value indicating whether to show a currency - /// Target currency - /// Currency string without exchange rate - protected string GetCurrencyString(decimal amount, bool showCurrency, Currency targetCurrency) - { - string result = string.Empty; - - var fmt = NumberFormatInfo.CurrentInfo; - try - { - fmt = CultureInfo.CreateSpecificCulture(targetCurrency.DisplayLocale).NumberFormat; - - if (!showCurrency) - fmt.CurrencySymbol = ""; - } - catch { } - - - if (targetCurrency.CustomFormatting.HasValue()) - { - result = amount.ToString(targetCurrency.CustomFormatting, fmt); - } - else - { - if (targetCurrency.DisplayLocale.HasValue()) - { - result = amount.ToString("C", fmt); - } - else - { - result = String.Format("{0} {1}", amount.ToString("N"), showCurrency ? targetCurrency.CurrencyCode : "").TrimEnd(); - return result; - } - } - - return result; - } - - #endregion - #region Methods public string FormatPrice(decimal price) @@ -98,16 +40,8 @@ public string FormatPrice(decimal price) public string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency) { var language = _workContext.WorkingLanguage; - bool priceIncludesTax = false; - switch (_workContext.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - priceIncludesTax = false; - break; - case TaxDisplayType.IncludingTax: - priceIncludesTax = true; - break; - } + bool priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax); } @@ -115,35 +49,17 @@ public string FormatPrice(decimal price, bool showCurrency, bool showTax) { var targetCurrency = _workContext.WorkingCurrency; var language = _workContext.WorkingLanguage; - bool priceIncludesTax = false; - switch (_workContext.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - priceIncludesTax = false; - break; - case TaxDisplayType.IncludingTax: - priceIncludesTax = true; - break; - } + bool priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax, showTax); } public string FormatPrice(decimal price, bool showCurrency, string currencyCode, bool showTax, Language language) { var currency = _currencyService.GetCurrencyByCode(currencyCode) ?? new Currency { CurrencyCode = currencyCode }; - bool priceIncludesTax = false; - switch (_workContext.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - priceIncludesTax = false; - break; - case TaxDisplayType.IncludingTax: - priceIncludesTax = true; - break; - } - - return FormatPrice(price, showCurrency, currency, - language, priceIncludesTax, showTax); + var priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + + return FormatPrice(price, showCurrency, currency, language, priceIncludesTax, showTax); } public string FormatPrice(decimal price, bool showCurrency, string currencyCode, Language language, bool priceIncludesTax) @@ -164,56 +80,30 @@ public string FormatPrice(decimal price, bool showCurrency, Currency targetCurre return FormatPrice(price, showCurrency, targetCurrency, language, priceIncludesTax, showTax); } - public string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax) - { - // Round before rendering (also take "BitCoin" into account, where more than 2 decimal places are relevant) - price = targetCurrency.CurrencyCode.IsCaseInsensitiveEqual("btc") ? Math.Round(price, 6) : Math.Round(price, 2); - - var currencyString = GetCurrencyString(price, showCurrency, targetCurrency); + public string FormatPrice(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax, bool showTax) + { + var formatted = new Money(price, targetCurrency).ToString(showCurrency); + if (showTax) { // Show tax suffix - string formatStr; - if (priceIncludesTax) - { - formatStr = _localizationService.GetResource("Products.InclTaxSuffix", language.Id, false); - if (string.IsNullOrEmpty(formatStr)) - { - formatStr = "{0} incl tax"; - } - } - else - { - formatStr = _localizationService.GetResource("Products.ExclTaxSuffix", language.Id, false); - if (string.IsNullOrEmpty(formatStr)) - { - formatStr = "{0} excl tax"; - } - } - return string.Format(formatStr, currencyString); - } - else - { - return currencyString; + var resKey = "Products." + (priceIncludesTax ? "InclTaxSuffix" : "ExclTaxSuffix"); + var taxFormatStr = _localizationService.GetResource(resKey, language.Id, false).NullEmpty() ?? (priceIncludesTax ? "{0} incl. tax" : "{0} excl. tax"); + + formatted = string.Format(taxFormatStr, formatted); } - } + return formatted; + } - public string FormatShippingPrice(decimal price, bool showCurrency) + + public string FormatShippingPrice(decimal price, bool showCurrency) { var targetCurrency = _workContext.WorkingCurrency; var language = _workContext.WorkingLanguage; - bool priceIncludesTax = false; - switch (_workContext.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - priceIncludesTax = false; - break; - case TaxDisplayType.IncludingTax: - priceIncludesTax = true; - break; - } + var priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + return FormatShippingPrice(price, showCurrency, targetCurrency, language, priceIncludesTax); } @@ -246,18 +136,9 @@ public string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency) { var targetCurrency = _workContext.WorkingCurrency; var language = _workContext.WorkingLanguage; - bool priceIncludesTax = false; - switch (_workContext.TaxDisplayType) - { - case TaxDisplayType.ExcludingTax: - priceIncludesTax = false; - break; - case TaxDisplayType.IncludingTax: - priceIncludesTax = true; - break; - } - return FormatPaymentMethodAdditionalFee(price, showCurrency, targetCurrency, - language, priceIncludesTax); + var priceIncludesTax = _workContext.TaxDisplayType == TaxDisplayType.IncludingTax; + + return FormatPaymentMethodAdditionalFee(price, showCurrency, targetCurrency, language, priceIncludesTax); } public string FormatPaymentMethodAdditionalFee(decimal price, bool showCurrency, Currency targetCurrency, Language language, bool priceIncludesTax) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs index 152391fff9..7ce8def364 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeExtensions.cs @@ -1,10 +1,7 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Xml; using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Localization; namespace SmartStore.Services.Catalog { @@ -26,7 +23,7 @@ public static bool ShouldHaveValues(this ProductVariantAttribute productVariantA productVariantAttribute.AttributeControlType == AttributeControlType.FileUpload) return false; - // all other attribute control types support values + // All other attribute control types support values. return true; } @@ -90,39 +87,5 @@ public static string AddProductAttribute(this ProductVariantAttribute pva, strin } return result; } - - /// - /// Searches the alias and returns values for fragments that begins with fieldPrefix - /// - /// Product variant attribute values - /// Field prefix - /// Language identifier - /// Localized value names mapped by field names - public static Dictionary GetMappedValuesFromAlias(this ICollection attributeValues, string fieldPrefix, int languageId) - { - Guard.NotNull(attributeValues, nameof(attributeValues)); - Guard.NotEmpty(fieldPrefix, nameof(fieldPrefix)); - - var result = new Dictionary(StringComparer.OrdinalIgnoreCase); - - if (!fieldPrefix.EndsWith(":")) - fieldPrefix = fieldPrefix + ":"; - - // TODO: do not use value alias, create a new attribute export field. Refers to issue #998. - - //foreach (var value in attributeValues.Where(x => x.Alias.HasValue())) - //{ - // foreach (var item in value.Alias.SplitSafe(null).Where(x => x.EmptyNull().StartsWith(fieldPrefix))) - // { - // var fieldName = item.Substring(fieldPrefix.Length); - // if (fieldName.HasValue() && !result.ContainsKey(fieldName)) - // { - // result.Add(fieldName, value.GetLocalized(x => x.Name, languageId, true, false)); - // } - // } - //} - - return result; - } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs index 435a49d283..1cfb95ee34 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs @@ -109,7 +109,7 @@ public string FormatAttributes(Product product, string attributes, if (pva.AttributeControlType == AttributeControlType.MultilineTextbox) { //multiline textbox - var attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId); + string attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId); //encode (if required) if (htmlEncode) attributeName = HttpUtility.HtmlEncode(attributeName); @@ -143,7 +143,7 @@ public string FormatAttributes(Product product, string attributes, //hyperlinks aren't allowed attributeText = fileName; } - var attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId); + string attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId); //encode (if required) if (htmlEncode) attributeName = HttpUtility.HtmlEncode(attributeName); diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index ec7665e87a..6c6c95eae0 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Data.Entity; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; @@ -145,6 +146,44 @@ public virtual void UpdateProductAttribute(ProductAttribute productAttribute) _requestCache.RemoveByPattern(PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY); } + public virtual Multimap GetExportFieldMappings(string fieldPrefix) + { + Guard.NotEmpty(fieldPrefix, nameof(fieldPrefix)); + + var result = new Multimap(StringComparer.OrdinalIgnoreCase); + + if (!fieldPrefix.EndsWith(":")) + { + fieldPrefix = fieldPrefix + ":"; + } + + var mappings = _productAttributeRepository.TableUntracked + .Where(x => !string.IsNullOrEmpty(x.ExportMappings)) + .Select(x => new + { + x.Id, + x.ExportMappings + }) + .ToList(); + + foreach (var mapping in mappings) + { + var rows = mapping.ExportMappings.SplitSafe(Environment.NewLine) + .Where(x => x.StartsWith(fieldPrefix, StringComparison.InvariantCultureIgnoreCase)); + + foreach (var row in rows) + { + var exportFieldName = row.Substring(fieldPrefix.Length).TrimEnd(); + if (exportFieldName.HasValue()) + { + result.Add(exportFieldName, mapping.Id); + } + } + } + + return result; + } + #endregion #region Product attribute options @@ -363,12 +402,14 @@ public virtual IEnumerable GetProductVariantAttrib (int)AttributeControlType.Boxes }; - var query = from x in _productVariantAttributeValueRepository.Table.Expand(y => y.ProductVariantAttribute.ProductAttribute) + var query = from x in _productVariantAttributeValueRepository.Table let attr = x.ProductVariantAttribute where productVariantAttributeValueIds.Contains(x.Id) && validTypeIds.Contains(attr.AttributeControlTypeId) orderby x.ProductVariantAttribute.DisplayOrder, x.DisplayOrder select x; + query = query.Include(y => y.ProductVariantAttribute.ProductAttribute); + return query.ToList(); }); } @@ -665,7 +706,7 @@ public virtual ProductVariantAttributeCombination GetProductVariantAttributeComb if (sku.IsEmpty()) return null; - var combination = _pvacRepository.Table.FirstOrDefault(x => x.Sku == sku && x.Product.Deleted == false); + var combination = _pvacRepository.Table.FirstOrDefault(x => x.Sku == sku && x.Product.Deleted == false && !x.Product.IsSystemProduct); return combination; } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index f84c7379c1..1a99142239 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -1,21 +1,18 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Core; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Media; using SmartStore.Core.Infrastructure; using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Tax; -using SmartStore.Core.Domain.Customers; namespace SmartStore.Services.Catalog { - public static class ProductExtensions + public static class ProductExtensions { public static ProductVariantAttributeCombination MergeWithCombination(this Product product, string selectedAttributes) { @@ -29,12 +26,16 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ if (selectedAttributes.IsEmpty()) return null; - // let's find appropriate record + // Let's find appropriate record. var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null && combination.IsActive) + if (combination != null && combination.IsActive) { - product.MergeWithCombination(combination); + product.MergeWithCombination(combination); + } + else if (product.MergedDataValues != null) + { + product.MergedDataValues.Clear(); } return combination; @@ -199,8 +200,9 @@ public static string FormatStockMessage(this Product product, ILocalizationServi public static bool DisplayDeliveryTimeAccordingToStock(this Product product, CatalogSettings catalogSettings) { Guard.NotNull(product, nameof(product)); + Guard.NotNull(catalogSettings, nameof(catalogSettings)); - if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) + if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) { if (catalogSettings.DeliveryTimeIdForEmptyStock.HasValue && product.StockQuantity <= 0) return true; @@ -211,13 +213,32 @@ public static bool DisplayDeliveryTimeAccordingToStock(this Product product, Cat return true; } - /// - /// Indicates whether the product is labeled as NEW. - /// - /// Product entity - /// Catalog settings - /// Whether the product is labeled as NEW - public static bool IsNew(this Product product, CatalogSettings catalogSettings) + public static int? GetDeliveryTimeIdAccordingToStock(this Product product, CatalogSettings catalogSettings) + { + Guard.NotNull(catalogSettings, nameof(catalogSettings)); + + if (product == null) + { + return null; + } + + if ((product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) + && catalogSettings.DeliveryTimeIdForEmptyStock.HasValue + && product.StockQuantity <= 0) + { + return catalogSettings.DeliveryTimeIdForEmptyStock.Value; + } + + return product.DeliveryTimeId; + } + + /// + /// Indicates whether the product is labeled as NEW. + /// + /// Product entity + /// Catalog settings + /// Whether the product is labeled as NEW + public static bool IsNew(this Product product, CatalogSettings catalogSettings) { if (catalogSettings.LabelAsNewForMaxDays.HasValue) { diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 81fc1ac527..ce67b25993 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -74,7 +74,7 @@ protected virtual int EnsureMutuallyRelatedProducts(List productIds) var mutualAssociations = ( from rp in _relatedProductRepository.Table join p in _productRepository.Table on rp.ProductId2 equals p.Id - where !p.Deleted && rp.ProductId2 == id1 + where rp.ProductId2 == id1 && !p.Deleted && !p.IsSystemProduct select rp).ToList(); foreach (int id2 in productIds) @@ -115,7 +115,7 @@ protected virtual int EnsureMutuallyCrossSellProducts(List productIds) var mutualAssociations = ( from rp in _crossSellProductRepository.Table join p in _productRepository.Table on rp.ProductId2 equals p.Id - where !p.Deleted && rp.ProductId2 == id1 + where rp.ProductId2 == id1 && !p.Deleted && !p.IsSystemProduct select rp).ToList(); foreach (int id2 in productIds) @@ -172,7 +172,7 @@ public virtual IList GetAllProductsDisplayedOnHomePage() var query = from p in _productRepository.Table orderby p.HomePageDisplayOrder - where p.Published && !p.Deleted && p.ShowOnHomePage + where p.Published && !p.Deleted && !p.IsSystemProduct && p.ShowOnHomePage select p; var products = query.ToList(); @@ -207,6 +207,17 @@ where productIds.Contains(p.Id) return products.OrderBySequence(productIds).ToList(); } + public virtual Product GetProductBySystemName(string systemName) + { + if (systemName.IsEmpty()) + { + return null; + } + + var product = _productRepository.Table.FirstOrDefault(x => x.SystemName == systemName && x.IsSystemProduct); + return product; + } + private IQueryable ApplyLoadFlags(IQueryable query, ProductLoadFlags flags) { if (flags.HasFlag(ProductLoadFlags.WithAttributeCombinations)) @@ -326,7 +337,7 @@ public virtual IList GetLowStockProducts() // Track inventory for product var query1 = from p in _productRepository.Table orderby p.MinStockQuantity - where !p.Deleted && + where !p.Deleted && !p.IsSystemProduct && p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && p.MinStockQuantity >= p.StockQuantity select p; @@ -335,7 +346,7 @@ orderby p.MinStockQuantity // Track inventory for product by product attributes var query2 = from p in _productRepository.Table from pvac in p.ProductVariantAttributeCombinations - where !p.Deleted && + where !p.Deleted && !p.IsSystemProduct && p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && pvac.StockQuantity <= 0 select p; @@ -363,7 +374,7 @@ public virtual Product GetProductBySku(string sku) var query = from p in _productRepository.Table orderby p.DisplayOrder, p.Id - where !p.Deleted && p.Sku == sku + where !p.Deleted && !p.IsSystemProduct && p.Sku == sku select p; var product = query.FirstOrDefault(); return product; @@ -378,8 +389,7 @@ public virtual Product GetProductByGtin(string gtin) var query = from p in _productRepository.Table orderby p.Id - where !p.Deleted && - p.Gtin == gtin + where !p.Deleted && !p.IsSystemProduct && p.Gtin == gtin select p; var product = query.FirstOrDefault(); return product; @@ -393,7 +403,7 @@ public virtual Product GetProductByManufacturerPartNumber(string manufacturerPar manufacturerPartNumber = manufacturerPartNumber.Trim(); var product = _productRepository.Table - .Where(x => !x.Deleted && x.ManufacturerPartNumber == manufacturerPartNumber) + .Where(x => !x.Deleted && !x.IsSystemProduct && x.ManufacturerPartNumber == manufacturerPartNumber) .OrderBy(x => x.Id) .FirstOrDefault(); @@ -408,7 +418,7 @@ public virtual Product GetProductByName(string name) name = name.Trim(); var product = _productRepository.Table - .Where(x => !x.Deleted && x.Name == name) + .Where(x => !x.Deleted && !x.IsSystemProduct && x.Name == name) .OrderBy(x => x.Id) .FirstOrDefault(); @@ -596,8 +606,9 @@ public virtual Multimap GetProductTagsByProductIds(int[] produc }); var map = new Multimap(); + var list = query.ToList(); - foreach (var item in query.ToList()) + foreach (var item in list) { foreach (var tag in item.Tags) map.Add(item.ProductId, tag); @@ -644,7 +655,7 @@ public virtual IList GetRelatedProductsByProductId1(int productI { var query = from rp in _relatedProductRepository.Table join p in _productRepository.Table on rp.ProductId2 equals p.Id - where rp.ProductId1 == productId1 && !p.Deleted && (showHidden || p.Published) + where rp.ProductId1 == productId1 && !p.Deleted && !p.IsSystemProduct && (showHidden || p.Published) orderby rp.DisplayOrder select rp; @@ -702,7 +713,7 @@ public virtual IList GetCrossSellProductsByProductId1(int prod { var query = from csp in _crossSellProductRepository.Table join p in _productRepository.Table on csp.ProductId2 equals p.Id - where csp.ProductId1 == productId1 && !p.Deleted && (showHidden || p.Published) + where csp.ProductId1 == productId1 && !p.Deleted && !p.IsSystemProduct && (showHidden || p.Published) orderby csp.Id select csp; @@ -716,7 +727,7 @@ public virtual IList GetCrossSellProductsByProductIds(IEnumera var query = from csp in _crossSellProductRepository.Table join p in _productRepository.Table on csp.ProductId2 equals p.Id - where productIds.Contains(csp.ProductId1) && !p.Deleted && (showHidden || p.Published) + where productIds.Contains(csp.ProductId1) && !p.Deleted && !p.IsSystemProduct && (showHidden || p.Published) orderby csp.Id select csp; @@ -1019,11 +1030,11 @@ public virtual IList GetBundleItems(int bundleProductId, var query = from pbi in _productBundleItemRepository.Table join p in _productRepository.Table on pbi.ProductId equals p.Id - where pbi.BundleProductId == bundleProductId && !p.Deleted && (showHidden || (pbi.Published && p.Published)) + where pbi.BundleProductId == bundleProductId && !p.Deleted && !p.IsSystemProduct && (showHidden || (pbi.Published && p.Published)) orderby pbi.DisplayOrder select pbi; - query = query.Expand(x => x.Product); + query = query.Include(x => x.Product); var bundleItemData = new List(); @@ -1039,11 +1050,11 @@ public virtual Multimap GetBundleItemsByProductIds(int[] var query = from pbi in _productBundleItemRepository.TableUntracked join p in _productRepository.TableUntracked on pbi.ProductId equals p.Id - where productIds.Contains(pbi.BundleProductId) && !p.Deleted && (showHidden || (pbi.Published && p.Published)) + where productIds.Contains(pbi.BundleProductId) && !p.Deleted && !p.IsSystemProduct && (showHidden || (pbi.Published && p.Published)) orderby pbi.DisplayOrder select pbi; - var map = query.Expand(x => x.Product) + var map = query.Include(x => x.Product) .ToList() .ToMultimap(x => x.BundleProductId, x => x); diff --git a/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs b/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs index 5ee34a2a5c..c694fcd7f4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/RecentlyViewedProductsService.cs @@ -89,7 +89,7 @@ public virtual IList GetRecentlyViewedProducts(int number) var productIds = GetRecentlyViewedProductsIds(number); var recentlyViewedProducts = _productService .GetProductsByIds(productIds.ToArray()) - .Where(x => x.Published && !x.Deleted && _aclService.Authorize(x)) + .Where(x => x.Published && !x.Deleted && !x.IsSystemProduct && _aclService.Authorize(x)) .ToList(); return recentlyViewedProducts; diff --git a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs index b8f94ad350..e6d3ed5d8a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs @@ -179,7 +179,7 @@ public virtual IList GetProductSpecificationAttri // Note: Join or Expand of SpecificationAttribute, both provides the same SQL. var joinedQuery = from psa in _productSpecificationAttributeRepository.Table - join sao in _specificationAttributeOptionRepository.Table.Expand(x => x.SpecificationAttribute) on psa.SpecificationAttributeOptionId equals sao.Id + join sao in _specificationAttributeOptionRepository.Table on psa.SpecificationAttributeOptionId equals sao.Id where psa.ProductId == productId select new { @@ -221,7 +221,6 @@ public virtual Multimap GetProductSpecificat Guard.NotNull(productIds, nameof(productIds)); var query = _productSpecificationAttributeRepository.TableUntracked - .Expand(x => x.SpecificationAttributeOption) .Expand(x => x.SpecificationAttributeOption.SpecificationAttribute) .Where(x => productIds.Contains(x.ProductId)); diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs index 4c0e1c05d0..71f2242f37 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs @@ -66,28 +66,28 @@ public static TPropType GetAttribute(this IGenericAttributeService ge Guard.NotNull(genericAttributeService, nameof(genericAttributeService)); Guard.NotEmpty(entityName, nameof(entityName)); - var props = genericAttributeService.GetAttributesForEntity(entityId, entityName); + var attrs = genericAttributeService.GetAttributesForEntity(entityId, entityName); // little hack here (only for unit testing). we should write expect-return rules in unit tests for such cases - if (props == null) + if (attrs == null) { - return default(TPropType); + return default; } - if (!props.Any(x => x.StoreId == storeId)) + if (!attrs.Any(x => x.StoreId == storeId)) { - return default(TPropType); + return default; } - var prop = props.FirstOrDefault(ga => - ga.StoreId == storeId && ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); //should be culture invariant + var attr = attrs.FirstOrDefault(x => + x.StoreId == storeId && x.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); // should be culture invariant - if (prop == null || prop.Value.IsEmpty()) + if (attr == null || attr.Value.IsEmpty()) { - return default(TPropType); + return default; } - return prop.Value.Convert(); + return attr.Value.Convert(); } } } diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs index 5c6a353ddb..2f0809277c 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs @@ -7,7 +7,6 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Events; -using SmartStore.Data; using SmartStore.Services.Orders; using SmartStore.Data.Caching; @@ -24,15 +23,14 @@ public GenericAttributeService( IEventPublisher eventPublisher, IRepository orderRepository) { - this._genericAttributeRepository = genericAttributeRepository; - this._eventPublisher = eventPublisher; - this._orderRepository = orderRepository; + _genericAttributeRepository = genericAttributeRepository; + _eventPublisher = eventPublisher; + _orderRepository = orderRepository; } public virtual void DeleteAttribute(GenericAttribute attribute) { - if (attribute == null) - throw new ArgumentNullException("attribute"); + Guard.NotNull(attribute, nameof(attribute)); int entityId = attribute.EntityId; string keyGroup = attribute.KeyGroup; @@ -57,10 +55,9 @@ public virtual GenericAttribute GetAttributeById(int attributeId) public virtual void InsertAttribute(GenericAttribute attribute) { - if (attribute == null) - throw new ArgumentNullException("attribute"); + Guard.NotNull(attribute, nameof(attribute)); - _genericAttributeRepository.Insert(attribute); + _genericAttributeRepository.Insert(attribute); if (attribute.KeyGroup.IsCaseInsensitiveEqual("Order") && attribute.EntityId != 0) { @@ -71,10 +68,9 @@ public virtual void InsertAttribute(GenericAttribute attribute) public virtual void UpdateAttribute(GenericAttribute attribute) { - if (attribute == null) - throw new ArgumentNullException("attribute"); + Guard.NotNull(attribute, nameof(attribute)); - _genericAttributeRepository.Update(attribute); + _genericAttributeRepository.Update(attribute); if (attribute.KeyGroup.IsCaseInsensitiveEqual("Order") && attribute.EntityId != 0) { diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index e6a92fc481..5eab6f9f14 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -1,32 +1,29 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using Newtonsoft.Json; +using SmartStore.ComponentModel; using SmartStore.Core.Caching; using SmartStore.Core.Configuration; using SmartStore.Core.Data; using SmartStore.Core.Domain.Configuration; -using SmartStore.Core.Events; -using System.Linq.Expressions; -using System.Reflection; -using SmartStore.ComponentModel; -using System.Collections; using SmartStore.Core.Logging; namespace SmartStore.Services.Configuration { - public partial class SettingService : ScopedServiceBase, ISettingService + public partial class SettingService : ScopedServiceBase, ISettingService { private const string SETTINGS_ALL_KEY = "setting:all"; private readonly IRepository _settingRepository; - private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; - public SettingService(ICacheManager cacheManager, IEventPublisher eventPublisher, IRepository settingRepository) + public SettingService(ICacheManager cacheManager, IRepository settingRepository) { _cacheManager = cacheManager; - _eventPublisher = eventPublisher; _settingRepository = settingRepository; Logger = NullLogger.Instance; @@ -63,6 +60,23 @@ protected virtual IDictionary GetAllCachedSettings() }); } + protected virtual PropertyInfo GetPropertyInfo(Expression> keySelector) + { + var member = keySelector.Body as MemberExpression; + if (member == null) + { + throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); + } + + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) + { + throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); + } + + return propInfo; + } + public virtual void InsertSetting(Setting setting, bool clearCache = true) { Guard.NotNull(setting, nameof(setting)); @@ -183,23 +197,8 @@ public virtual bool SettingExists( int storeId = 0) where T : ISettings, new() { - var member = keySelector.Body as MemberExpression; - if (member == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); - } - - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } - - string key = typeof(T).Name + "." + propInfo.Name; + var propInfo = GetPropertyInfo(keySelector); + var key = string.Concat(typeof(T).Name, ".", propInfo.Name); string setting = GetSettingByKey(key, storeId: storeId); return setting != null; @@ -373,8 +372,8 @@ protected virtual void SaveSettingCore(ISettings settings, int storeId = 0) if (settingType.HasAttribute(true)) { - //SaveSettingsJson(settings); - //return; + SaveSettingsJson(settings); + return; } /* We do not clear cache after each setting update. @@ -405,24 +404,10 @@ public virtual void SaveSetting( int storeId = 0, bool clearCache = true) where T : ISettings, new() { - var member = keySelector.Body as MemberExpression; - if (member == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); - } + var propInfo = GetPropertyInfo(keySelector); + var key = string.Concat(typeof(T).Name, ".", propInfo.Name); - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } - - string key = typeof(T).Name + "." + propInfo.Name; - // Duck typing is not supported in C#. That's why we're using dynamic type + // Duck typing is not supported in C#. That's why we're using dynamic type. var fastProp = FastProperty.GetProperty(propInfo, PropertyCachingStrategy.EagerCached); dynamic value = fastProp.GetValue(settings); @@ -436,9 +421,13 @@ public virtual void UpdateSetting( int storeId = 0) where T : ISettings, new() { if (overrideForStore || storeId == 0) - SaveSetting(settings, keySelector, storeId, true); + { + SaveSetting(settings, keySelector, storeId, false); + } else if (storeId > 0) + { DeleteSetting(settings, keySelector, storeId); + } } public virtual void DeleteSetting(Setting setting) @@ -483,23 +472,8 @@ public virtual void DeleteSetting( Expression> keySelector, int storeId = 0) where T : ISettings, new() { - var member = keySelector.Body as MemberExpression; - if (member == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); - } - - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } - - string key = typeof(T).Name + "." + propInfo.Name; + var propInfo = GetPropertyInfo(keySelector); + var key = string.Concat(typeof(T).Name, ".", propInfo.Name); DeleteSetting(key, storeId); } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs index 570baeba76..8f83fb33a8 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerContentService.cs @@ -20,8 +20,7 @@ public CustomerContentService(IRepository contentRepository, IE public virtual void DeleteCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); + Guard.NotNull(content, nameof(content)); _contentRepository.Delete(content); } @@ -33,14 +32,15 @@ orderby c.CreatedOnUtc descending where !approved.HasValue || c.IsApproved == approved && (customerId == 0 || c.CustomerId == customerId) select c; + var content = query.ToList(); return content; } - public virtual IList GetAllCustomerContent(int customerId, bool? approved, - DateTime? fromUtc = null, DateTime? toUtc = null) where T : CustomerContent + public virtual IList GetAllCustomerContent(int customerId, bool? approved, DateTime? fromUtc = null, DateTime? toUtc = null) where T : CustomerContent { var query = _contentRepository.Table; + if (approved.HasValue) query = query.Where(c => c.IsApproved == approved); if (customerId > 0) @@ -49,7 +49,9 @@ public virtual IList GetAllCustomerContent(int customerId, bool? approved, query = query.Where(c => fromUtc.Value <= c.CreatedOnUtc); if (toUtc.HasValue) query = query.Where(c => toUtc.Value >= c.CreatedOnUtc); + query = query.OrderByDescending(c => c.CreatedOnUtc); + var content = query.OfType().ToList(); return content; } @@ -65,18 +67,16 @@ public virtual CustomerContent GetCustomerContentById(int contentId) public virtual void InsertCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); + Guard.NotNull(content, nameof(content)); - _contentRepository.Insert(content); + _contentRepository.Insert(content); } public virtual void UpdateCustomerContent(CustomerContent content) { - if (content == null) - throw new ArgumentNullException("content"); + Guard.NotNull(content, nameof(content)); - _contentRepository.Update(content); + _contentRepository.Update(content); } } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs index 34a7cc9aa0..e4515e9241 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtensions.cs @@ -3,18 +3,23 @@ using System.Diagnostics; using System.Linq; using System.Xml; -using SmartStore.Core.Domain.Common; +using SmartStore.Core; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Services.Common; using SmartStore.Services.Localization; using SmartStore.Services.Orders; +using SmartStore.Utilities; namespace SmartStore.Services.Customers { public static class CustomerExtentions { + private static readonly string[] _systemColors = new string[] { "primary", "secondary", "success", "info", "warning", "danger", "light", "dark" }; + /// /// Gets a value indicating whether customer is in a certain customer role /// @@ -143,20 +148,9 @@ public static string GetFullName(this Customer customer) if (customer == null) return string.Empty; - var firstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName).NullEmpty(); - var lastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName).NullEmpty(); - - if (firstName != null && lastName != null) - { - return firstName + " " + lastName; - } - else if (firstName != null) + if (customer.FullName.HasValue()) { - return firstName; - } - else if (lastName != null) - { - return lastName; + return customer.FullName; } string name = customer.BillingAddress?.GetFullName(); @@ -173,33 +167,62 @@ public static string GetFullName(this Customer customer) } /// - /// Formats the customer name + /// Formats the customer name. /// - /// Source - /// Formatted text + /// Customer entity. + /// Formatted customer name. public static string FormatUserName(this Customer customer) { return FormatUserName(customer, false); } /// - /// Formats the customer name + /// Formats the customer name. /// - /// Source - /// Strip too long customer name - /// Formatted text + /// Customer entity. + /// Whether to strip too long customer name. + /// Formatted customer name. public static string FormatUserName(this Customer customer, bool stripTooLong) { + var engine = EngineContext.Current; + + var userName = customer.FormatUserName( + engine.Resolve(), + engine.Resolve(), + stripTooLong); + + return userName; + } + + /// + /// Formats the customer name. + /// + /// Customer entity. + /// Customer settings. + /// Localizer. + /// Whether to strip too long customer name. + /// Formatted customer name. + public static string FormatUserName( + this Customer customer, + CustomerSettings customerSettings, + Localizer T, + bool stripTooLong) + { + Guard.NotNull(customerSettings, nameof(customerSettings)); + Guard.NotNull(T, nameof(T)); + if (customer == null) + { return string.Empty; - + } if (customer.IsGuest()) { - return EngineContext.Current.Resolve().GetResource("Customer.Guest"); + return T("Customer.Guest"); } - string result = string.Empty; - switch (EngineContext.Current.Resolve().CustomerNameFormat) + var result = string.Empty; + + switch (customerSettings.CustomerNameFormat) { case CustomerNameFormat.ShowEmails: result = customer.Email; @@ -210,59 +233,56 @@ public static string FormatUserName(this Customer customer, bool stripTooLong) case CustomerNameFormat.ShowUsernames: result = customer.Username; break; - case CustomerNameFormat.ShowFirstName: - result = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - break; - case CustomerNameFormat.ShowNameAndCity: - { - var firstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - var lastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); - var city = customer.GetAttribute(SystemCustomerAttributeNames.City); - - if (firstName.IsEmpty()) - { - var address = customer.Addresses.FirstOrDefault(); - if (address != null) - { - firstName = address.FirstName; - lastName = address.LastName; - city = address.City; - } - } - - result = firstName; - if (lastName.HasValue()) - { - result = "{0} {1}.".FormatWith(result, lastName.First()); - } - - if (city.HasValue()) - { - var from = EngineContext.Current.Resolve().GetResource("Common.ComingFrom"); - result = "{0} {1} {2}".FormatWith(result, from, city); - } - } - break; + case CustomerNameFormat.ShowFirstName: + result = customer.FirstName; + break; + case CustomerNameFormat.ShowNameAndCity: + { + var firstName = customer.FirstName; + var lastName = customer.LastName; + var city = customer.GetAttribute(SystemCustomerAttributeNames.City); + + if (firstName.IsEmpty()) + { + var address = customer.Addresses.FirstOrDefault(); + if (address != null) + { + firstName = address.FirstName; + lastName = address.LastName; + city = address.City; + } + } + + result = firstName; + if (lastName.HasValue()) + { + result = "{0} {1}.".FormatInvariant(result, lastName.First()); + } + + if (city.HasValue()) + { + var from = T("Common.ComingFrom"); + result = "{0} {1} {2}".FormatInvariant(result, from, city); + } + } + break; default: break; } - if (stripTooLong && result.HasValue()) + var maxLength = customerSettings.CustomerNameFormatMaxLength; + if (stripTooLong && maxLength > 0 && result != null && result.Length > maxLength) { - int maxLength = EngineContext.Current.Resolve().CustomerNameFormatMaxLength; - if (maxLength > 0 && result.Length > maxLength) - { - result = result.Truncate(maxLength, "..."); - } + result = result.Truncate(maxLength, "..."); } return result; } - /// - /// Find any email address of customer - /// - public static string FindEmail(this Customer customer) + /// + /// Find any email address of customer + /// + public static string FindEmail(this Customer customer) { if (customer != null) { @@ -274,6 +294,47 @@ public static string FindEmail(this Customer customer) return null; } + public static Language GetLanguage(this Customer customer) + { + if (customer == null) + return null; + + var language = EngineContext.Current.Resolve().GetLanguageById(customer.GetAttribute(SystemCustomerAttributeNames.LanguageId)); + + if (language == null || !language.Published) + { + language = EngineContext.Current.Resolve().WorkingLanguage; + } + + return language; + } + + /// + /// Returns a random system color suitable as avatar background color. + /// + /// Customer entity. + /// Generic attribute service. + /// Random system color. + public static string GetAvatarColor(this Customer customer, IGenericAttributeService genericAttributeService) + { + Guard.NotNull(genericAttributeService, nameof(genericAttributeService)); + + string color = null; + + if (customer != null) + { + color = customer.GetAttribute(SystemCustomerAttributeNames.AvatarColor, genericAttributeService); + if (color.IsEmpty()) + { + var rnd = CommonHelper.GenerateRandomInteger(0, _systemColors.Length - 1); + color = _systemColors[rnd]; + + genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AvatarColor, color); + } + } + + return color; + } #region Shopping cart @@ -430,5 +491,5 @@ public static void RemoveGiftCardCouponCode(this Customer customer, string coupo } #endregion - } + } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs new file mode 100644 index 0000000000..92710cc92e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs @@ -0,0 +1,119 @@ +using System; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; + +namespace SmartStore.Services.Customers +{ + public class CustomerSearchQuery : ICloneable + { + /// + /// Customer registration from; null to load all customers. Default: null. + /// + public DateTime? RegistrationFromUtc { get; set; } + + /// + /// Customer registration to; null to load all customers. Default: null. + /// + public DateTime? RegistrationToUtc { get; set; } + + /// + /// Customer last activity date (from)s. Default: null. + /// + public DateTime? LastActivityFromUtc { get; set; } + + /// + /// A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers. Default: null. + /// + public int[] CustomerRoleIds { get; set; } + + /// + /// Affiliate identifie. Default: null. + /// + public int? AffiliateId { get; set; } + + /// + /// Email. Default: null. + /// + public string Email { get; set; } + + /// + /// UserName. Default: null. + /// + public string Username { get; set; } + + /// + /// Phone. Default: null. + /// + public string Phone { get; set; } + + /// + /// ZipPostalCode. Default: null. + /// + public string ZipPostalCode { get; set; } + + /// + /// CustomerNumber. Default: null. + /// + public string CustomerNumber { get; set; } + + /// + /// Day of birth. Default: null. + /// + public int? DayOfBirth { get; set; } + + /// + /// Month of birth. Default: null. + /// + public int? MonthOfBirth { get; set; } + + /// + /// Password format. Default: null. + /// + public PasswordFormat? PasswordFormat { get; set; } + + /// + /// Whether to only load customers with shopping cart. Default: false (meaning: no matter) + /// + public bool OnlyWithCart { get; set; } + + /// + /// What shopping cart type to filter; used when 'HasCart' param is 'true'. Default: null. + /// + public ShoppingCartType? CartType { get; set; } + + /// + /// Whether only (soft)-deleted records should be loaded. Default: false (meaning: only undeleted) + /// + public bool? Deleted { get; set; } = false; + + /// + /// Whether only system account records should be loaded. Default: false (meaning: ignore system accounts) + /// + public bool? IsSystemAccount { get; set; } = false; + + /// + /// Searches in FullName (FirstName + LastName) and Company fields. Default: null. + /// + public string SearchTerm { get; set; } + + /// + /// Page index. Default: 0. + /// + public int PageIndex { get; set; } + + /// + /// Page index. Default: 50. + /// + public int PageSize { get; set; } = 50; + + public CustomerSearchQuery Clone() + { + return (CustomerSearchQuery)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index aec3a43344..37981c8195 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -7,7 +7,6 @@ using System.Web; using SmartStore.Collections; using SmartStore.Core; -using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -15,13 +14,13 @@ using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Events; using SmartStore.Core.Localization; using SmartStore.Core.Fakes; using SmartStore.Data.Caching; using SmartStore.Services.Common; using SmartStore.Services.Localization; using SmartStore.Core.Logging; +using SmartStore.Services.Messages; namespace SmartStore.Services.Customers { @@ -36,6 +35,9 @@ public partial class CustomerService : ICustomerService private readonly ICommonServices _services; private readonly HttpContextBase _httpContext; private readonly IUserAgent _userAgent; + private readonly CustomerSettings _customerSettings; + private readonly Lazy _messageModelProvider; + private readonly Lazy _gdprTool; public CustomerService( IRepository customerRepository, @@ -46,17 +48,23 @@ public CustomerService( RewardPointsSettings rewardPointsSettings, ICommonServices services, HttpContextBase httpContext, - IUserAgent userAgent) + IUserAgent userAgent, + CustomerSettings customerSettings, + Lazy messageModelProvider, + Lazy gdprTool) { - this._customerRepository = customerRepository; - this._customerRoleRepository = customerRoleRepository; - this._gaRepository = gaRepository; - this._rewardPointsHistoryRepository = rewardPointsHistoryRepository; - this._genericAttributeService = genericAttributeService; - this._rewardPointsSettings = rewardPointsSettings; - this._services = services; - this._httpContext = httpContext; - this._userAgent = userAgent; + _customerRepository = customerRepository; + _customerRoleRepository = customerRoleRepository; + _gaRepository = gaRepository; + _rewardPointsHistoryRepository = rewardPointsHistoryRepository; + _genericAttributeService = genericAttributeService; + _rewardPointsSettings = rewardPointsSettings; + _services = services; + _httpContext = httpContext; + _userAgent = userAgent; + _customerSettings = customerSettings; + _messageModelProvider = messageModelProvider; + _gdprTool = gdprTool; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -68,196 +76,192 @@ public CustomerService( #region Customers - public virtual IPagedList GetAllCustomers( - DateTime? registrationFrom, - DateTime? registrationTo, - int[] customerRoleIds, - string email, - string username, - string firstName, - string lastName, - int dayOfBirth, - int monthOfBirth, - string company, - string phone, - string zipPostalCode, - bool loadOnlyWithShoppingCart, - ShoppingCartType? sct, - int pageIndex, - int pageSize) - { - var query = _customerRepository.Table; - if (registrationFrom.HasValue) - query = query.Where(c => registrationFrom.Value <= c.CreatedOnUtc); - if (registrationTo.HasValue) - query = query.Where(c => registrationTo.Value >= c.CreatedOnUtc); - query = query.Where(c => !c.Deleted); - if (customerRoleIds != null && customerRoleIds.Length > 0) - query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id).Intersect(customerRoleIds).Count() > 0); - if (!String.IsNullOrWhiteSpace(email)) - query = query.Where(c => c.Email.Contains(email)); - if (!String.IsNullOrWhiteSpace(username)) - query = query.Where(c => c.Username.Contains(username)); - if (!String.IsNullOrWhiteSpace(firstName)) - { - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.FirstName && - z.Attribute.Value.Contains(firstName))) - .Select(z => z.Customer); - } - if (!String.IsNullOrWhiteSpace(lastName)) - { - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.LastName && - z.Attribute.Value.Contains(lastName))) - .Select(z => z.Customer); - } - //date of birth is stored as a string into database. - //we also know that date of birth is stored in the following format YYYY-MM-DD (for example, 1983-02-18). - //so let's search it as a string - if (dayOfBirth > 0 && monthOfBirth > 0) - { - //both are specified - string dateOfBirthStr = monthOfBirth.ToString("00", CultureInfo.InvariantCulture) + "-" + dayOfBirth.ToString("00", CultureInfo.InvariantCulture); - //EndsWith is not supported by SQL Server Compact - //so let's use the following workaround http://social.msdn.microsoft.com/Forums/is/sqlce/thread/0f810be1-2132-4c59-b9ae-8f7013c0cc00 - - //we also cannot use Length function in SQL Server Compact (not supported in this context) - //z.Attribute.Value.Length - dateOfBirthStr.Length = 5 - //dateOfBirthStr.Length = 5 - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.DateOfBirth && - z.Attribute.Value.Substring(5, 5) == dateOfBirthStr)) - .Select(z => z.Customer); - } - else if (dayOfBirth > 0) - { - //only day is specified - string dateOfBirthStr = dayOfBirth.ToString("00", CultureInfo.InvariantCulture); - //EndsWith is not supported by SQL Server Compact - //so let's use the following workaround http://social.msdn.microsoft.com/Forums/is/sqlce/thread/0f810be1-2132-4c59-b9ae-8f7013c0cc00 - - //we also cannot use Length function in SQL Server Compact (not supported in this context) - //z.Attribute.Value.Length - dateOfBirthStr.Length = 8 - //dateOfBirthStr.Length = 2 - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.DateOfBirth && - z.Attribute.Value.Substring(8, 2) == dateOfBirthStr)) - .Select(z => z.Customer); - } - else if (monthOfBirth > 0) - { - //only month is specified - string dateOfBirthStr = "-" + monthOfBirth.ToString("00", CultureInfo.InvariantCulture) + "-"; - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.DateOfBirth && - z.Attribute.Value.Contains(dateOfBirthStr))) - .Select(z => z.Customer); - } - //search by company - if (!String.IsNullOrWhiteSpace(company)) - { - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.Company && - z.Attribute.Value.Contains(company))) - .Select(z => z.Customer); - } - //search by phone - if (!String.IsNullOrWhiteSpace(phone)) - { - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.Phone && - z.Attribute.Value.Contains(phone))) - .Select(z => z.Customer); - } - //search by zip - if (!String.IsNullOrWhiteSpace(zipPostalCode)) - { - query = query - .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) - .Where((z => z.Attribute.KeyGroup == "Customer" && - z.Attribute.Key == SystemCustomerAttributeNames.ZipPostalCode && - z.Attribute.Value.Contains(zipPostalCode))) - .Select(z => z.Customer); - } + public virtual IPagedList SearchCustomers(CustomerSearchQuery q) + { + Guard.NotNull(q, nameof(q)); - if (loadOnlyWithShoppingCart) - { - int? sctId = null; - if (sct.HasValue) - sctId = (int)sct.Value; + var query = _customerRepository.Table; - query = sct.HasValue ? - query.Where(c => c.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == sctId).Count() > 0) : - query.Where(c => c.ShoppingCartItems.Count() > 0); - } - - query = query.OrderByDescending(c => c.CreatedOnUtc); + if (q.Email.HasValue()) + { + query = query.Where(c => c.Email.Contains(q.Email)); + } - var customers = new PagedList(query, pageIndex, pageSize); - return customers; - } + if (q.Username.HasValue()) + { + query = query.Where(c => c.Username.Contains(q.Username)); + } - public virtual IPagedList GetAllCustomers(int affiliateId, int pageIndex, int pageSize) - { - var query = _customerRepository.Table; - query = query.Where(c => !c.Deleted); - query = query.Where(c => c.AffiliateId == affiliateId); - query = query.OrderByDescending(c => c.CreatedOnUtc); + if (q.CustomerNumber.HasValue()) + { + query = query.Where(c => c.CustomerNumber.Contains(q.CustomerNumber)); + } - var customers = new PagedList(query, pageIndex, pageSize); - return customers; - } + if (q.AffiliateId.GetValueOrDefault() > 0) + { + query = query.Where(c => c.AffiliateId == q.AffiliateId.Value); + } + + if (q.SearchTerm.HasValue()) + { + if (_customerSettings.CompanyEnabled) + { + query = query.Where(c => c.FullName.Contains(q.SearchTerm) || c.Company.Contains(q.SearchTerm)); + } + else + { + query = query.Where(c => c.FullName.Contains(q.SearchTerm)); + } + } + + if (q.DayOfBirth.GetValueOrDefault() > 0) + { + query = query.Where(c => c.BirthDate.Value.Day == q.DayOfBirth.Value); + } + + if (q.MonthOfBirth.GetValueOrDefault() > 0) + { + query = query.Where(c => c.BirthDate.Value.Month == q.MonthOfBirth.Value); + } + + if (q.RegistrationFromUtc.HasValue) + { + query = query.Where(c => q.RegistrationFromUtc.Value <= c.CreatedOnUtc); + } + + if (q.RegistrationToUtc.HasValue) + { + query = query.Where(c => q.RegistrationToUtc.Value >= c.CreatedOnUtc); + } + + if (q.LastActivityFromUtc.HasValue) + { + query = query.Where(c => q.LastActivityFromUtc.Value <= c.LastActivityDateUtc); + } + + if (q.CustomerRoleIds != null && q.CustomerRoleIds.Length > 0) + { + query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id).Intersect(q.CustomerRoleIds).Count() > 0); + } + + if (q.Deleted.HasValue) + { + query = query.Where(c => c.Deleted == q.Deleted.Value); + } + + if (q.IsSystemAccount.HasValue) + { + query = q.IsSystemAccount.Value == true + ? query.Where(c => !string.IsNullOrEmpty(c.SystemName)) + : query.Where(c => string.IsNullOrEmpty(c.SystemName)); + } + + if (q.PasswordFormat.HasValue) + { + int passwordFormatId = (int)q.PasswordFormat.Value; + query = query.Where(c => c.PasswordFormatId == passwordFormatId); + } + + // Search by phone + if (q.Phone.HasValue()) + { + query = query + .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) + .Where((z => z.Attribute.KeyGroup == "Customer" && + z.Attribute.Key == SystemCustomerAttributeNames.Phone && + z.Attribute.Value.Contains(q.Phone))) + .Select(z => z.Customer); + } + + // Search by zip + if (q.ZipPostalCode.HasValue()) + { + query = query + .Join(_gaRepository.Table, x => x.Id, y => y.EntityId, (x, y) => new { Customer = x, Attribute = y }) + .Where((z => z.Attribute.KeyGroup == "Customer" && + z.Attribute.Key == SystemCustomerAttributeNames.ZipPostalCode && + z.Attribute.Value.Contains(q.ZipPostalCode))) + .Select(z => z.Customer); + } + + if (q.OnlyWithCart) + { + int? sctId = null; + if (q.CartType.HasValue) + { + sctId = (int)q.CartType.Value; + } + + query = q.CartType.HasValue ? + query.Where(c => c.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == sctId).Count() > 0) : + query.Where(c => c.ShoppingCartItems.Count() > 0); + } + + query = query.OrderByDescending(c => c.CreatedOnUtc); + + var customers = new PagedList(query, q.PageIndex, q.PageSize); - public virtual IList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat) + return customers; + } + + public virtual IPagedList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat) { - int passwordFormatId = (int)passwordFormat; + var q = new CustomerSearchQuery + { + PasswordFormat = passwordFormat, + PageIndex = 0, + PageSize = 500 + }; - var query = _customerRepository.Table; - query = query.Where(c => c.PasswordFormatId == passwordFormatId); - query = query.OrderByDescending(c => c.CreatedOnUtc); - var customers = query.ToList(); - return customers; + var customers = SearchCustomers(q); + return customers; } - public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, - int[] customerRoleIds, int pageIndex, int pageSize) + public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize) { - var query = _customerRepository.Table; - query = query.Where(c => lastActivityFromUtc <= c.LastActivityDateUtc); - query = query.Where(c => !c.Deleted); - if (customerRoleIds != null && customerRoleIds.Length > 0) - query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id).Intersect(customerRoleIds).Count() > 0); - - query = query.OrderByDescending(c => c.LastActivityDateUtc); - var customers = new PagedList(query, pageIndex, pageSize); + var q = new CustomerSearchQuery + { + LastActivityFromUtc = lastActivityFromUtc, + CustomerRoleIds = customerRoleIds, + IsSystemAccount = false, + PageIndex = pageIndex, + PageSize = pageSize + }; + + var customers = SearchCustomers(q); + + customers.AlterQuery(x => x.OrderByDescending(c => c.LastActivityDateUtc)); + return customers; } public virtual void DeleteCustomer(Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); if (customer.IsSystemAccount) - throw new SmartException(string.Format("System customer account ({0}) could not be deleted", customer.SystemName)); + throw new SmartException(string.Format("System customer account ({0}) cannot not be deleted", customer.SystemName)); + // Soft delete customer.Deleted = true; - UpdateCustomer(customer); + + // Anonymize IP addresses + var language = customer.GetLanguage(); + + _gdprTool.Value.AnonymizeData(customer, x => x.LastIpAddress, IdentifierDataType.IpAddress, language); + + foreach (var post in customer.ForumPosts) + { + _gdprTool.Value.AnonymizeData(post, x => x.IPAddress, IdentifierDataType.IpAddress, language); + } + + // Customer Content + foreach (var item in customer.CustomerContent) + { + _gdprTool.Value.AnonymizeData(item, x => x.IpAddress, IdentifierDataType.IpAddress, language); + } + + UpdateCustomer(customer); } public virtual Customer GetCustomerById(int customerId) @@ -321,6 +325,7 @@ public virtual Customer GetCustomerByEmail(string email) orderby c.Id where c.Email == email select c; + var customer = query.FirstOrDefault(); return customer; } @@ -334,6 +339,7 @@ public virtual Customer GetCustomerBySystemName(string systemName) orderby c.Id where c.SystemName == systemName select c; + var customer = query.FirstOrDefault(); return customer; } @@ -429,62 +435,64 @@ from c in Customers.DefaultIfEmpty() && a.Value.Contains(clientIdent) // SQLCE doesn't like ntext in WHERE clauses select c; } - + return query.FirstOrDefault(); } } public virtual void InsertCustomer(Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); - _customerRepository.Insert(customer); + _customerRepository.Insert(customer); } public virtual void UpdateCustomer(Customer customer) { - if (customer == null) - throw new ArgumentNullException("customer"); + Guard.NotNull(customer, nameof(customer)); - _customerRepository.Update(customer); + _customerRepository.Update(customer); } - public virtual void ResetCheckoutData(Customer customer, int storeId, - bool clearCouponCodes = false, bool clearCheckoutAttributes = false, - bool clearRewardPoints = false, bool clearShippingMethod = true, - bool clearPaymentMethod = true) + public virtual void ResetCheckoutData( + Customer customer, + int storeId, + bool clearCouponCodes = false, + bool clearCheckoutAttributes = false, + bool clearRewardPoints = false, + bool clearShippingMethod = true, + bool clearPaymentMethod = true, + bool clearCreditBalance = false) { - if (customer == null) - throw new ArgumentNullException(); + Guard.NotNull(customer, nameof(customer)); - //clear entered coupon codes - if (clearCouponCodes) + if (clearCouponCodes) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DiscountCouponCode, null); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.GiftCardCouponCodes, null); } - //clear checkout attributes if (clearCheckoutAttributes) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CheckoutAttributes, null); } - //clear reward points flag if (clearRewardPoints) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, false, storeId); } - //clear selected shipping method + if (clearCreditBalance) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.UseCreditBalanceDuringCheckout, decimal.Zero, storeId); + } + if (clearShippingMethod) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedShippingOption, null, storeId); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.OfferedShippingOptions, null, storeId); } - //clear selected payment method if (clearPaymentMethod) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, null, storeId); @@ -619,23 +627,22 @@ from inner in c_inner.DefaultIfEmpty() #region Customer roles - public virtual void DeleteCustomerRole(CustomerRole customerRole) + public virtual void DeleteCustomerRole(CustomerRole role) { - if (customerRole == null) - throw new ArgumentNullException("customerRole"); + Guard.NotNull(role, nameof(role)); - if (customerRole.IsSystemRole) + if (role.IsSystemRole) throw new SmartException("System role could not be deleted"); - _customerRoleRepository.Delete(customerRole); + _customerRoleRepository.Delete(role); } - public virtual CustomerRole GetCustomerRoleById(int customerRoleId) + public virtual CustomerRole GetCustomerRoleById(int roleId) { - if (customerRoleId == 0) + if (roleId == 0) return null; - return _customerRoleRepository.GetById(customerRoleId); + return _customerRoleRepository.GetById(roleId); } public virtual CustomerRole GetCustomerRoleBySystemName(string systemName) @@ -663,20 +670,18 @@ orderby cr.Name return customerRoles; } - public virtual void InsertCustomerRole(CustomerRole customerRole) + public virtual void InsertCustomerRole(CustomerRole role) { - if (customerRole == null) - throw new ArgumentNullException("customerRole"); + Guard.NotNull(role, nameof(role)); - _customerRoleRepository.Insert(customerRole); + _customerRoleRepository.Insert(role); } - public virtual void UpdateCustomerRole(CustomerRole customerRole) + public virtual void UpdateCustomerRole(CustomerRole role) { - if (customerRole == null) - throw new ArgumentNullException("customerRole"); + Guard.NotNull(role, nameof(role)); - _customerRoleRepository.Update(customerRole); + _customerRoleRepository.Update(role); } #endregion diff --git a/src/Libraries/SmartStore.Services/Customers/Events/CustomerAnonymizedEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerAnonymizedEvent.cs new file mode 100644 index 0000000000..b25350d730 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerAnonymizedEvent.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using SmartStore.Core; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore.Services.Customers +{ + /// + /// TODO + /// + public class CustomerAnonymizedEvent + { + private readonly IGdprTool _gdprTool; + + public CustomerAnonymizedEvent(Customer customer, IGdprTool gdprTool) + { + Guard.NotNull(customer, nameof(customer)); + + Customer = customer; + _gdprTool = gdprTool; + } + + public Customer Customer { get; private set; } + + /// + /// TODO + /// + /// + /// + /// + /// + public void AnonymizeData(TEntity entity, Expression> expression, IdentifierDataType type, Language language = null) + where TEntity : BaseEntity + { + _gdprTool.AnonymizeData(entity, expression, type, language); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/Events/CustomerExportedEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerExportedEvent.cs new file mode 100644 index 0000000000..0c143c8b2f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerExportedEvent.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Customers +{ + public class CustomerExportedEvent + { + public CustomerExportedEvent(Customer customer, IDictionary result) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(result, nameof(result)); + + Customer = customer; + Result = result; + } + + public Customer Customer { get; private set; } + public IDictionary Result { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs rename to src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs diff --git a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs new file mode 100644 index 0000000000..b8a52c9666 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Domain.Customers; +using SmartStore.Services.Messages; +using SmartStore.Services.Common; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.News; +using SmartStore.Core.Domain.Blogs; +using SmartStore.Core.Domain.Polls; +using SmartStore.Services.Catalog; +using SmartStore.Services.Forums; +using SmartStore.Services.Customers; +using System.Linq.Expressions; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; +using SmartStore.Services.Orders; +using SmartStore.Core.Localization; +using SmartStore.Core.Domain.Localization; +using System.Globalization; +using SmartStore.Services.Localization; +using SmartStore.Utilities; +using System.Text; +using System.Net; +using System.Net.Sockets; +using SmartStore.Core.Logging; + +namespace SmartStore.Services.Customers +{ + public enum IdentifierDataType + { + Text, + LongText, + Name, + UserName, + EmailAddress, + Url, + IpAddress, + PhoneNumber, + Address, + PostalCode, + DateTime + } + + public partial class GdprTool : IGdprTool + { + private readonly IMessageModelProvider _messageModelProvider; + private readonly IGenericAttributeService _genericAttributeService; + private readonly IShoppingCartService _shoppingCartService; + private readonly IForumService _forumService; + private readonly IBackInStockSubscriptionService _backInStockSubscriptionService; + private readonly ILanguageService _languageService; + private readonly ICommonServices _services; + + public static DateTime MinDate = new DateTime(1900, 1, 1); + + const string AnonymousEmail = "anonymous@example.com"; + + public GdprTool( + IMessageModelProvider messageModelProvider, + IGenericAttributeService genericAttributeService, + IShoppingCartService shoppingCartService, + IForumService forumService, + IBackInStockSubscriptionService backInStockSubscriptionService, + ILanguageService languageService, + ICommonServices services) + { + _messageModelProvider = messageModelProvider; + _genericAttributeService = genericAttributeService; + _shoppingCartService = shoppingCartService; + _forumService = forumService; + _backInStockSubscriptionService = backInStockSubscriptionService; + _languageService = languageService; + _services = services; + + T = NullLocalizer.InstanceEx; + Logger = NullLogger.Instance; + } + + public LocalizerEx T { get; set; } + + public ILogger Logger { get; set; } + + public virtual IDictionary ExportCustomer(Customer customer) + { + Guard.NotNull(customer, nameof(customer)); + + var ignoreMemberNames = new string[] + { + "WishlistUrl", "EditUrl", "PasswordRecoveryURL", + "BillingAddress.NameLine", "BillingAddress.StreetLine", "BillingAddress.CityLine", "BillingAddress.CountryLine", + "ShippingAddress.NameLine", "ShippingAddress.StreetLine", "ShippingAddress.CityLine", "ShippingAddress.CountryLine" + }; + + var model = _messageModelProvider.CreateModelPart(customer, true, ignoreMemberNames) as IDictionary; + + if (model != null) + { + // Roles + model["CustomerRoles"] = customer.CustomerRoles.Select(x => x.Name).ToArray(); + + // Generic attributes + var attributes = _genericAttributeService.GetAttributesForEntity(customer.Id, "Customer"); + if (attributes.Any()) + { + model["Attributes"] = _messageModelProvider.CreateModelPart(attributes, true); + } + + // Order history + var orders = customer.Orders.Where(x => !x.Deleted); + if (orders.Any()) + { + ignoreMemberNames = new string[] + { + "Disclaimer", "ConditionsOfUse", "Url", "CheckoutAttributes", + "Items.DownloadUrl", + "Items.Product.Description", "Items.Product.Url", "Items.Product.Thumbnail", "Items.Product.ThumbnailLg", + "Items.BundleItems.Product.Description", "Items.BundleItems.Product.Url", "Items.BundleItems.Product.Thumbnail", "Items.BundleItems.Product.ThumbnailLg", + "Billing.NameLine", "Billing.StreetLine", "Billing.CityLine", "Billing.CountryLine", + "Shipping.NameLine", "Shipping.StreetLine", "Shipping.CityLine", "Shipping.CountryLine" + }; + model["Orders"] = orders.Select(x => _messageModelProvider.CreateModelPart(x, true, ignoreMemberNames)).ToList(); + } + + // Return Request + var returnRequests = customer.ReturnRequests; + if (returnRequests.Any()) + { + model["ReturnRequests"] = returnRequests.Select(x => _messageModelProvider.CreateModelPart(x, true, "Url")).ToList(); + } + + // Wallet + var walletHistory = customer.WalletHistory; + if (walletHistory.Any()) + { + model["WalletHistory"] = walletHistory.Select(x => _messageModelProvider.CreateModelPart(x, true, "WalletUrl")).ToList(); + } + + // Forum topics + var forumTopics = customer.ForumTopics; + if (forumTopics.Any()) + { + model["ForumTopics"] = forumTopics.Select(x => _messageModelProvider.CreateModelPart(x, true, "Url")).ToList(); + } + + // Forum posts + var forumPosts = customer.ForumPosts; + if (forumPosts.Any()) + { + model["ForumPosts"] = forumPosts.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); + } + + // Product reviews + var productReviews = customer.CustomerContent.OfType(); + if (productReviews.Any()) + { + model["ProductReviews"] = productReviews.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); + } + + // News comments + var newsComments = customer.CustomerContent.OfType(); + if (newsComments.Any()) + { + model["NewsComments"] = newsComments.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); + } + + // Blog comments + var blogComments = customer.CustomerContent.OfType(); + if (blogComments.Any()) + { + model["BlogComments"] = blogComments.Select(x => _messageModelProvider.CreateModelPart(x, true)).ToList(); + } + + // Product review helpfulness + var helpfulness = customer.CustomerContent.OfType(); + if (helpfulness.Any()) + { + ignoreMemberNames = new string[] { "CustomerId", "UpdatedOn" }; + model["ProductReviewHelpfulness"] = helpfulness.Select(x => _messageModelProvider.CreateModelPart(x, true, ignoreMemberNames)).ToList(); + } + + // Poll voting + var pollVotings = customer.CustomerContent.OfType(); + if (pollVotings.Any()) + { + ignoreMemberNames = new string[] { "CustomerId", "UpdatedOn" }; + model["PollVotings"] = pollVotings.Select(x => _messageModelProvider.CreateModelPart(x, true, ignoreMemberNames)).ToList(); + } + + // Forum subscriptions + var forumSubscriptions = _forumService.GetAllSubscriptions(customer.Id, 0, 0, 0, int.MaxValue); + if (forumSubscriptions.Any()) + { + model["ForumSubscriptions"] = forumSubscriptions.Select(x => _messageModelProvider.CreateModelPart(x, true, "CustomerId")).ToList(); + } + + // BackInStock subscriptions + var backInStockSubscriptions = _backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(customer.Id, 0, 0, int.MaxValue); + if (backInStockSubscriptions.Any()) + { + model["BackInStockSubscriptions"] = backInStockSubscriptions.Select(x => _messageModelProvider.CreateModelPart(x, true, "CustomerId")).ToList(); + } + + // INFO: we're not going to export: + // - Private messages + // - Activity log + // It doesn't feel right and GDPR rules are not very clear about this. Let's wait and see :-) + + // Publish event to give plugin devs a chance to attach external data. + _services.EventPublisher.Publish(new CustomerExportedEvent(customer, model)); + } + + return model; + } + + public virtual void AnonymizeCustomer(Customer customer, bool pseudomyzeContent) + { + Guard.NotNull(customer, nameof(customer)); + + var language = customer.GetLanguage(); + var customerName = customer.GetFullName() ?? customer.Username ?? customer.FindEmail(); + + using (var scope = new DbContextScope(_services.DbContext, autoCommit: false)) + { + // Set to deleted + customer.Deleted = true; + + // Unassign roles + customer.CustomerRoles.Clear(); + customer.CustomerRoles.Add(_services.Resolve().GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests)); + + // Delete shopping cart & wishlist (TBD: (mc) Really?!?) + _shoppingCartService.DeleteExpiredShoppingCartItems(DateTime.UtcNow, customer.Id); + + // Delete forum subscriptions + var forumSubscriptions = _forumService.GetAllSubscriptions(customer.Id, 0, 0, 0, int.MaxValue); + foreach (var forumSub in forumSubscriptions) + { + _forumService.DeleteSubscription(forumSub); + } + + // Delete BackInStock subscriptions + var backInStockSubscriptions = _backInStockSubscriptionService.GetAllSubscriptionsByCustomerId(customer.Id, 0, 0, int.MaxValue); + foreach (var stockSub in backInStockSubscriptions) + { + _backInStockSubscriptionService.DeleteSubscription(stockSub); + } + + // Generic attributes + var attributes = _genericAttributeService.GetAttributesForEntity(customer.Id, "Customer"); + foreach (var attr in attributes) + { + // we don't need to mask generic attrs, we just delete them. + _genericAttributeService.DeleteAttribute(attr); + } + + // Customer Data + AnonymizeData(customer, x => x.Username, IdentifierDataType.UserName, language); + AnonymizeData(customer, x => x.Email, IdentifierDataType.EmailAddress, language); + AnonymizeData(customer, x => x.LastIpAddress, IdentifierDataType.IpAddress, language); + if (pseudomyzeContent) + { + AnonymizeData(customer, x => x.AdminComment, IdentifierDataType.LongText, language); + AnonymizeData(customer, x => x.LastLoginDateUtc, IdentifierDataType.DateTime, language); + AnonymizeData(customer, x => x.LastActivityDateUtc, IdentifierDataType.DateTime, language); + } + + // Addresses + foreach (var address in customer.Addresses) + { + AnonymizeAddress(address, language); + } + + // Private messages + if (pseudomyzeContent) + { + var privateMessages = _forumService.GetAllPrivateMessages(0, customer.Id, 0, null, null, null, 0, int.MaxValue); + foreach (var msg in privateMessages) + { + AnonymizeData(msg, x => x.Subject, IdentifierDataType.Text, language); + AnonymizeData(msg, x => x.Text, IdentifierDataType.LongText, language); + } + } + + // Forum topics + if (pseudomyzeContent) + { + foreach (var topic in customer.ForumTopics) + { + AnonymizeData(topic, x => x.Subject, IdentifierDataType.Text, language); + } + } + + // Forum posts + foreach (var post in customer.ForumPosts) + { + AnonymizeData(post, x => x.IPAddress, IdentifierDataType.IpAddress, language); + if (pseudomyzeContent) + { + AnonymizeData(post, x => x.Text, IdentifierDataType.LongText, language); + } + } + + // Customer Content + foreach (var item in customer.CustomerContent) + { + AnonymizeData(item, x => x.IpAddress, IdentifierDataType.IpAddress, language); + + if (pseudomyzeContent) + { + switch (item) + { + case ProductReview c: + AnonymizeData(c, x => x.ReviewText, IdentifierDataType.LongText, language); + AnonymizeData(c, x => x.Title, IdentifierDataType.Text, language); + break; + case NewsComment c: + AnonymizeData(c, x => x.CommentText, IdentifierDataType.LongText, language); + AnonymizeData(c, x => x.CommentTitle, IdentifierDataType.Text, language); + break; + case BlogComment c: + AnonymizeData(c, x => x.CommentText, IdentifierDataType.LongText, language); + break; + } + } + } + + //// Anonymize Order IPs + //// TBD: Don't! Doesn't feel right because of fraud detection etc. + //foreach (var order in customer.Orders) + //{ + // AnonymizeData(order, x => x.CustomerIp, IdentifierDataType.IpAddress, language); + //} + + // SAVE!!! + //_services.DbContext.DetachAll(); // TEST + scope.Commit(); + + // Log + Logger.Info(T("Gdpr.Anonymize.Success", language.Id, customerName)); + } + } + + private void AnonymizeAddress(Address address, Language language) + { + AnonymizeData(address, x => x.Address1, IdentifierDataType.Address, language); + AnonymizeData(address, x => x.Address2, IdentifierDataType.Address, language); + AnonymizeData(address, x => x.City, IdentifierDataType.Address, language); + AnonymizeData(address, x => x.Company, IdentifierDataType.Address, language); + AnonymizeData(address, x => x.Email, IdentifierDataType.EmailAddress, language); + AnonymizeData(address, x => x.FaxNumber, IdentifierDataType.PhoneNumber, language); + AnonymizeData(address, x => x.FirstName, IdentifierDataType.Name, language); + AnonymizeData(address, x => x.LastName, IdentifierDataType.Name, language); + AnonymizeData(address, x => x.PhoneNumber, IdentifierDataType.PhoneNumber, language); + AnonymizeData(address, x => x.ZipPostalCode, IdentifierDataType.PostalCode, language); + } + + public virtual void AnonymizeData(TEntity entity, Expression> expression, IdentifierDataType type, Language language = null) + where TEntity : BaseEntity + { + Guard.NotNull(entity, nameof(entity)); + Guard.NotNull(expression, nameof(expression)); + + var originalValue = expression.Compile().Invoke(entity); + object maskedValue = null; + + if (originalValue is DateTime d) + { + maskedValue = MinDate; + } + else if (originalValue is string s) + { + if (s.IsEmpty()) + { + return; + } + + language = language ?? (entity as Customer)?.GetLanguage(); + + switch (type) + { + case IdentifierDataType.Address: + case IdentifierDataType.Name: + case IdentifierDataType.Text: + maskedValue = T("Gdpr.DeletedText", language.Id).Text; + break; + case IdentifierDataType.LongText: + maskedValue = T("Gdpr.DeletedLongText", language.Id).Text; + break; + case IdentifierDataType.EmailAddress: + //maskedValue = s.Hash(Encoding.ASCII, true) + "@anony.mous"; + maskedValue = HashCodeCombiner.Start() + .Add(entity.GetHashCode()) + .Add(s) + .CombinedHashString + "@anony.mous"; + break; + case IdentifierDataType.Url: + maskedValue = "https://anony.mous"; + break; + case IdentifierDataType.IpAddress: + maskedValue = AnonymizeIpAddress(s); + break; + case IdentifierDataType.UserName: + maskedValue = T("Gdpr.Anonymous", language.Id).Text.ToLower(); + break; + case IdentifierDataType.PhoneNumber: + maskedValue = "555-00000"; + break; + case IdentifierDataType.PostalCode: + maskedValue = "00000"; + break; + case IdentifierDataType.DateTime: + maskedValue = MinDate.ToString(CultureInfo.InvariantCulture); + break; + } + } + + if (maskedValue != null) + { + var pi = expression.ExtractPropertyInfo(); + pi.SetValue(entity, maskedValue); + } + } + + /// + /// Returns an anonymized IPv4 or IPv6 address. + /// + /// The IPv4 or IPv6 address to be anonymized. + /// The anonymized IP address. + protected virtual string AnonymizeIpAddress(string ipAddress) + { + try + { + var ip = IPAddress.Parse(ipAddress); + + switch (ip.AddressFamily) + { + case AddressFamily.InterNetwork: + break; + case AddressFamily.InterNetworkV6: + // Map to IPv4 first + ip = ip.MapToIPv4(); + break; + default: + // we only support IPv4 and IPv6 + return "0.0.0.0"; + } + + // Keep the first 3 bytes and append ".0" + return string.Join(".", ip.GetAddressBytes().Take(3)) + ".0"; + } + catch + { + return null; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs index 5b3fccfb4a..7921eb281d 100644 --- a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs @@ -13,66 +13,37 @@ namespace SmartStore.Services.Customers /// public partial interface ICustomerService { - #region Customers + #region Customers - /// - /// Gets all customers - /// - /// Customer registration from; null to load all customers - /// Customer registration to; null to load all customers - /// A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers; - /// Email; null to load all customers - /// Username; null to load all customers - /// First name; null to load all customers - /// Last name; null to load all customers - /// Day of birth; 0 to load all customers - /// Month of birth; 0 to load all customers - /// Company; null to load all customers - /// Phone; null to load all customers - /// Phone; null to load all customers - /// Value indicating whther to load customers only with shopping cart - /// Value indicating what shopping cart type to filter; userd when 'loadOnlyWithShoppingCart' param is 'true' - /// Page index - /// Page size - /// Customer collection - IPagedList GetAllCustomers(DateTime? registrationFrom, - DateTime? registrationTo, int[] customerRoleIds, string email, string username, - string firstName, string lastName, int dayOfBirth, int monthOfBirth, - string company, string phone, string zipPostalCode, - bool loadOnlyWithShoppingCart, ShoppingCartType? sct, int pageIndex, int pageSize); - - /// - /// Gets all customers by affiliate identifier - /// - /// Affiliate identifier - /// Page index - /// Page size - /// Customers - IPagedList GetAllCustomers(int affiliateId, int pageIndex, int pageSize); + /// + /// Finds customer records matching all criteria specified by + /// + /// The filter query + /// Customer collection + IPagedList SearchCustomers(CustomerSearchQuery q); - /// - /// Gets all customers by customer format (including deleted ones) - /// - /// Password format - /// Customers - IList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat); + /// + /// Gets all customers by customer format (including deleted ones) + /// + /// Password format + /// Customers + IPagedList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat); - /// - /// Gets online customers - /// - /// Customer last activity date (from) - /// A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers; - /// Page index - /// Page size - /// Customer collection - IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, - int[] customerRoleIds, int pageIndex, int pageSize); + /// + /// Gets online customers + /// + /// Customer last activity date (from) + /// A list of customer role identifiers to filter by (at least one match); pass null or empty list in order to load all customers; + /// Page index + /// Page size + /// Customer collection + IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize); - /// - /// Delete a customer - /// - /// Customer - void DeleteCustomer(Customer customer); + /// + /// Delete a customer + /// + /// Customer + void DeleteCustomer(Customer customer); /// /// Gets a customer @@ -109,7 +80,7 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, Customer GetCustomerByEmail(string email); /// - /// Get customer by system role + /// Get customer by system name /// /// System name /// Customer @@ -152,20 +123,22 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, /// Customer void UpdateCustomer(Customer customer); - /// - /// Reset data required for checkout - /// - /// Customer + /// + /// Reset data required for checkout + /// + /// Customer /// Store identifier - /// A value indicating whether to clear coupon code - /// A value indicating whether to clear selected checkout attributes - /// A value indicating whether to clear "Use reward points" flag - /// A value indicating whether to clear selected shipping method - /// A value indicating whether to clear selected payment method + /// A value indicating whether to clear coupon code + /// A value indicating whether to clear selected checkout attributes + /// A value indicating whether to clear "Use reward points" flag + /// A value indicating whether to clear selected shipping method + /// A value indicating whether to clear selected payment method + /// A value indicating whether to clear credit balance. void ResetCheckoutData(Customer customer, int storeId, bool clearCouponCodes = false, bool clearCheckoutAttributes = false, bool clearRewardPoints = false, bool clearShippingMethod = true, - bool clearPaymentMethod = true); + bool clearPaymentMethod = true, + bool clearCreditBalance = false); /// /// Delete guest customer records diff --git a/src/Libraries/SmartStore.Services/Customers/IGdprTool.cs b/src/Libraries/SmartStore.Services/Customers/IGdprTool.cs new file mode 100644 index 0000000000..febf53638e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/IGdprTool.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using SmartStore.Core; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore.Services.Customers +{ + public interface IGdprTool + { + /// + /// Exports all data stored for a customer into a dictionary. Exported data contains all + /// personal data, addresses, order history, reviews, forum posts, private messages etc. + /// + /// The customer to export data for. + /// The exported data + /// This method fulfills the "GDPR Data Portability" requirement. + IDictionary ExportCustomer(Customer customer); + + /// + /// Anonymizes a customer's (personal) data. + /// + /// The customer to anonymize. + /// + /// This method fulfills the "GDPR Right to be forgotten" requirement. + void AnonymizeCustomer(Customer customer, bool pseudomyzeContent); + + /// + /// TODO + /// + /// + /// + /// + /// + /// + void AnonymizeData(TEntity entity, Expression> expression, IdentifierDataType type, Language language = null) where TEntity : BaseEntity; + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 31a36989a4..15b0cdcf13 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -91,9 +91,9 @@ protected override void Import(ImportExecuteContext context) var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - + var allCustomerNumbers = new HashSet( - _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), + _customerRepository.Table.Where(x => !String.IsNullOrEmpty(x.CustomerNumber)).Select(x => x.CustomerNumber), StringComparer.OrdinalIgnoreCase); var allCustomerRoles = _customerRoleRepository.Table @@ -120,7 +120,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessCustomers(context, batch, allAffiliateIds); + ProcessCustomers(context, batch, allAffiliateIds, allCustomerNumbers); } catch (Exception exception) { @@ -157,7 +157,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces); } catch (Exception exception) { @@ -202,7 +202,8 @@ protected override void Import(ImportExecuteContext context) protected virtual int ProcessCustomers( ImportExecuteContext context, IEnumerable> batch, - List allAffiliateIds) + List allAffiliateIds, + HashSet allCustomerNumbers) { _customerRepository.AutoCommitEnabled = true; @@ -280,6 +281,17 @@ protected virtual int ProcessCustomers( row.SetProperty(context.Result, (x) => x.CustomerGuid); row.SetProperty(context.Result, (x) => x.Username); row.SetProperty(context.Result, (x) => x.Email); + row.SetProperty(context.Result, (x) => x.FirstName); + row.SetProperty(context.Result, (x) => x.LastName); + + if (_customerSettings.TitleEnabled) + row.SetProperty(context.Result, (x) => x.Title); + + if (_customerSettings.CompanyEnabled) + row.SetProperty(context.Result, (x) => x.Company); + + if (_customerSettings.DateOfBirthEnabled) + row.SetProperty(context.Result, (x) => x.BirthDate); if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) { @@ -304,6 +316,21 @@ protected virtual int ProcessCustomers( customer.AffiliateId = affiliateId; } + string customerNumber = null; + + if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && row.IsTransient) + customerNumber = row.Entity.Id.ToString(); + else if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.Enabled && !row.IsTransient && row.HasDataValue("CustomerNumber")) + customerNumber = row.GetDataValue("CustomerNumber"); + + if (customerNumber.HasValue() || !allCustomerNumbers.Contains(customerNumber)) + { + row.Entity.CustomerNumber = customerNumber; + + if (!customerNumber.IsEmpty()) + allCustomerNumbers.Add(customerNumber); + } + if (row.IsTransient) { _customerRepository.Insert(customer); @@ -483,26 +510,16 @@ protected virtual int ProcessGenericAttributes( ImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, - Dictionary, int> allStateProvinces, - HashSet allCustomerNumbers) + Dictionary, int> allStateProvinces) { foreach (var row in batch) { - SaveAttribute(row, SystemCustomerAttributeNames.FirstName); - SaveAttribute(row, SystemCustomerAttributeNames.LastName); - if (_dateTimeSettings.AllowCustomersToSetTimeZone) SaveAttribute(row, SystemCustomerAttributeNames.TimeZoneId); if (_customerSettings.GenderEnabled) SaveAttribute(row, SystemCustomerAttributeNames.Gender); - if (_customerSettings.DateOfBirthEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.DateOfBirth); - - if (_customerSettings.CompanyEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.Company); - if (_customerSettings.StreetAddressEnabled) SaveAttribute(row, SystemCustomerAttributeNames.StreetAddress); @@ -545,21 +562,6 @@ protected virtual int ProcessGenericAttributes( { SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); } - - string customerNumber = null; - - if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet) - customerNumber = row.Entity.Id.ToString(); - else - customerNumber = row.GetDataValue("CustomerNumber"); - - if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) - { - SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); - - if (!customerNumber.IsEmpty()) - allCustomerNumbers.Add(customerNumber); - } } return _services.DbContext.SaveChanges(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs index e6dcd55405..0d935149e8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs @@ -22,7 +22,7 @@ public DataExportTask( public void Execute(TaskExecutionContext ctx) { - var profileId = ctx.ScheduleTask.Alias.ToInt(); + var profileId = ctx.ScheduleTaskHistory.ScheduleTask.Alias.ToInt(); var profile = _exportProfileService.GetExportProfileById(profileId); // Load provider. diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 3b5d1bf471..fa6461e8cb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -82,7 +82,8 @@ public partial class DataExporter : IDataExporter private readonly Lazy _deliveryTimeService; private readonly Lazy _quantityUnitService; private readonly Lazy _catalogSearchService; - private readonly Lazy _productUrlHelper; + private readonly Lazy _downloadService; + private readonly Lazy _productUrlHelper; private readonly Lazy>_customerRepository; private readonly Lazy> _subscriptionRepository; @@ -129,7 +130,8 @@ public DataExporter( Lazy deliveryTimeService, Lazy quantityUnitService, Lazy catalogSearchService, - Lazy productUrlHelper, + Lazy downloadService, + Lazy productUrlHelper, Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, @@ -173,6 +175,7 @@ public DataExporter( _deliveryTimeService = deliveryTimeService; _quantityUnitService = quantityUnitService; _catalogSearchService = catalogSearchService; + _downloadService = downloadService; _productUrlHelper = productUrlHelper; _customerRepository = customerRepository; @@ -687,7 +690,8 @@ public virtual ProductExportContext CreateProductExportContext( x => _productService.Value.GetBundleItemsByProductIds(x, showHidden), x => _pictureService.Value.GetPicturesByProductIds(x, maxPicturesPerProduct, true), x => _productService.Value.GetProductPicturesByProductIds(x), - x => _productService.Value.GetProductTagsByProductIds(x) + x => _productService.Value.GetProductTagsByProductIds(x), + x => _downloadService.Value.GetDownloadsByEntityIds(x, nameof(Product)) ); return context; @@ -1013,7 +1017,22 @@ private IQueryable GetNewsLetterSubscriptionQuery(DataEx if (ctx.Filter.IsActiveSubscriber.HasValue) query = query.Where(x => x.Active == ctx.Filter.IsActiveSubscriber.Value); - if (ctx.Filter.CreatedFrom.HasValue) + if (ctx.Filter.WorkingLanguageId != null && ctx.Filter.WorkingLanguageId != 0) + { + var defaultLanguage = _languageService.Value.GetAllLanguages().FirstOrDefault(); + var isDefaultLanguage = ctx.Filter.WorkingLanguageId == defaultLanguage.Id; + + if (isDefaultLanguage) + { + query = query.Where(x => x.WorkingLanguageId == 0 || x.WorkingLanguageId == ctx.Filter.WorkingLanguageId); + } + else + { + query = query.Where(x => x.WorkingLanguageId == ctx.Filter.WorkingLanguageId); + } + } + + if (ctx.Filter.CreatedFrom.HasValue) { var createdFrom = _services.DateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _services.DateTimeHelper.CurrentTimeZone); query = query.Where(x => createdFrom <= x.CreatedOnUtc); @@ -1151,20 +1170,9 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) // Init base things that are even required for preview. Init all other things (regular export) in ExportCoreOuter. List result = null; - if (ctx.Projection.CurrencyId.HasValue) - ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId.Value); - else - ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; - - if (ctx.Projection.CustomerId.HasValue) - ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; - - if (ctx.Projection.LanguageId.HasValue) - ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); - else - ctx.ContextLanguage = _services.WorkContext.WorkingLanguage; + ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId ?? 0) ?? _services.WorkContext.WorkingCurrency; + ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId ?? 0) ?? _services.WorkContext.CurrentCustomer; + ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId ?? 0) ?? _services.WorkContext.WorkingLanguage; ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); ctx.Languages = _languageService.Value.GetAllLanguages(true).ToDictionary(x => x.Id, x => x); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs index abeb1514d0..3415948ae2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -38,6 +38,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de SendManually = false, To = email, Subject = deployment.EmailSubject.NaIfEmpty(), + Body = deployment.EmailSubject.NaIfEmpty(), CreatedOnUtc = DateTime.UtcNow, EmailAccountId = deployment.EmailAccountId }; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 24ce121d28..8bc039898c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -30,6 +30,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; +using SmartStore.Services.Customers; namespace SmartStore.Services.DataExchange.Export { @@ -38,11 +39,9 @@ public partial class DataExporter private readonly string[] _orderCustomerAttributes = new string[] { SystemCustomerAttributeNames.Gender, - SystemCustomerAttributeNames.DateOfBirth, SystemCustomerAttributeNames.VatNumber, SystemCustomerAttributeNames.VatNumberStatusId, SystemCustomerAttributeNames.TimeZoneId, - SystemCustomerAttributeNames.CustomerNumber, SystemCustomerAttributeNames.ImpersonatedCustomerId }; @@ -570,6 +569,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, string seNam dynamic result = new DynamicEntity(product); result.AppliedDiscounts = null; + result.Downloads = null; result.TierPrices = null; result.ProductAttributes = null; result.ProductAttributeCombinations = null; @@ -602,7 +602,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, string seNam ToDeliveryTime(ctx, result, product.DeliveryTimeId); ToQuantityUnit(ctx, result, product.QuantityUnitId); - result._Localized = GetLocalized(ctx, product, + result._Localized = GetLocalized(ctx, product, x => x.Name, x => x.ShortDescription, x => x.FullDescription, @@ -644,7 +644,10 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen if (productContext.Combination != null) { var pictureIds = productContext.Combination.GetAssignedPictureIds(); - productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); + if (pictureIds.Any()) + { + productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); + } attributesXml = productContext.Combination.AttributesXml; variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(attributesXml); @@ -701,7 +704,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen var node = _categoryService.Value.GetCategoryTree(pc.CategoryId, true, ctx.Store.Id); if (node != null) { - categoryPath = _categoryService.Value.GetCategoryPath(node, ctx.Projection.LanguageId, false, " > "); + categoryPath = _categoryService.Value.GetCategoryPath(node, ctx.Projection.LanguageId, null, " > "); } } @@ -819,7 +822,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen .ToList(); } - dynObject.ProductTags = productTags + if (product.IsDownload) + { + var downloads = ctx.ProductExportContext.Downloads.GetOrLoad(product.Id); + + dynObject.Downloads = downloads + .Select(x => ToDynamic(ctx, x)) + .ToList(); + } + + dynObject.ProductTags = productTags .Select(x => { dynamic dyn = new DynamicEntity(x); @@ -1049,7 +1061,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Discount discount) return result; } - private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) + private dynamic ToDynamic(DataExporterContext ctx, Download download) + { + if (download == null) + return null; + + dynamic result = new DynamicEntity(download); + return result; + } + + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) { if (psa == null) return null; @@ -1353,24 +1374,7 @@ private List Convert(DataExporterContext ctx, Customer customer) .ToList(); dynObject._HasNewsletterSubscription = ctx.NewsletterSubscriptions.Contains(customer.Email, StringComparer.CurrentCultureIgnoreCase); - - var attrFirstName = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.FirstName); - var attrLastName = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.LastName); - - string firstName = (attrFirstName == null ? "" : attrFirstName.Value); - string lastName = (attrLastName == null ? "" : attrLastName.Value); - - if (firstName.IsEmpty() && lastName.IsEmpty()) - { - var address = customer.Addresses.FirstOrDefault(); - if (address != null) - { - firstName = address.FirstName; - lastName = address.LastName; - } - } - - dynObject._FullName = firstName.Grow(lastName, " "); + dynObject._FullName = customer.GetFullName(); if (_customerSettings.Value.AllowCustomersToUploadAvatars) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 0371702eec..7c64bce18e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -49,20 +49,24 @@ public static string GetName(this Provider provider, ILocalizat /// Folder path public static string GetExportFolder(this ExportProfile profile, bool content = false, bool create = false) { - var path = CommonHelper.MapPath(string.Concat(profile.FolderName, content ? "/Content" : "")); + Guard.IsTrue(profile.FolderName.EmptyNull().Length > 2, nameof(profile.FolderName), "The export folder name must be at least 3 characters long."); - if (create && !System.IO.Directory.Exists(path)) - System.IO.Directory.CreateDirectory(path); + var path = CommonHelper.MapPath(string.Concat(profile.FolderName, content ? "/Content" : "")); - return path; - } + if (create && !System.IO.Directory.Exists(path)) + { + System.IO.Directory.CreateDirectory(path); + } - /// - /// Get log file path for an export profile - /// - /// Export profile - /// Log file path - public static string GetExportLogPath(this ExportProfile profile) + return path; + } + + /// + /// Get log file path for an export profile + /// + /// Export profile + /// Log file path + public static string GetExportLogPath(this ExportProfile profile) { return Path.Combine(profile.GetExportFolder(), "log.txt"); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index ed71ce4a8b..342c3c8109 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -78,7 +78,7 @@ public virtual ExportProfile InsertExportProfile( { task = new ScheduleTask { - CronExpression = "0 */6 * * *", // every six hours + CronExpression = "0 */6 * * *", // Every six hours. Type = typeof(DataExportTask).AssemblyQualifiedNameWithoutVersion(), Enabled = false, StopOnError = false, @@ -88,7 +88,6 @@ public virtual ExportProfile InsertExportProfile( else { task = cloneProfile.ScheduleTask.Clone(); - task.LastEndUtc = task.LastStartUtc = task.LastSuccessUtc = null; } task.Name = string.Concat(name, " Task"); @@ -227,10 +226,10 @@ public virtual void UpdateExportProfile(ExportProfile profile) profile.FolderName = FileSystemHelper.ValidateRootPath(profile.FolderName); - if (profile.FolderName == "~/") - { - throw new SmartException("Invalid export folder name."); - } + if (!FileSystemHelper.IsSafeRootPath(profile.FolderName)) + { + throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.FolderName.Validate")); + } _exportProfileRepository.Update(profile); } @@ -246,7 +245,14 @@ public virtual void DeleteExportProfile(ExportProfile profile, bool force = fals int scheduleTaskId = profile.SchedulingTaskId; var folder = profile.GetExportFolder(); - _exportProfileRepository.Delete(profile); + var deployments = profile.Deployments.Where(x => !x.IsTransientRecord()).ToList(); + if (deployments.Any()) + { + _exportDeploymentRepository.DeleteRange(deployments); + _exportDeploymentRepository.Context.SaveChanges(); + } + + _exportProfileRepository.Delete(profile); var scheduleTask = _scheduleTaskService.GetTaskById(scheduleTaskId); _scheduleTaskService.DeleteTask(scheduleTask); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs index 93f41a6d5a..c28f89bff8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs @@ -294,6 +294,7 @@ public void WriteDeliveryTime(dynamic deliveryTime, string node) _writer.Write("DisplayLocale", entity.DisplayLocale); _writer.Write("ColorHexValue", entity.ColorHexValue); _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("IsDefault", entity.IsDefault.ToString()); WriteLocalized(deliveryTime); @@ -501,7 +502,6 @@ public void WriteProduct(dynamic product, string node) _writer.Write("RequiredProductIds", entity.RequiredProductIds); _writer.Write("AutomaticallyAddRequiredProducts", entity.AutomaticallyAddRequiredProducts.ToString()); _writer.Write("IsDownload", entity.IsDownload.ToString()); - _writer.Write("DownloadId", entity.DownloadId.ToString()); _writer.Write("UnlimitedDownloads", entity.UnlimitedDownloads.ToString()); _writer.Write("MaxNumberOfDownloads", entity.MaxNumberOfDownloads.ToString()); _writer.Write("DownloadExpirationDays", entity.DownloadExpirationDays.HasValue ? entity.DownloadExpirationDays.Value.ToString() : ""); @@ -530,7 +530,9 @@ public void WriteProduct(dynamic product, string node) _writer.Write("AllowBackInStockSubscriptions", entity.AllowBackInStockSubscriptions.ToString()); _writer.Write("OrderMinimumQuantity", entity.OrderMinimumQuantity.ToString()); _writer.Write("OrderMaximumQuantity", entity.OrderMaximumQuantity.ToString()); - _writer.Write("HideQuantityControl", entity.HideQuantityControl.ToString()); + _writer.Write("QuantityStep", entity.QuantityStep.ToString()); + _writer.Write("QuantiyControlType", ((int)entity.QuantiyControlType).ToString()); + _writer.Write("HideQuantityControl", entity.HideQuantityControl.ToString()); _writer.Write("AllowedQuantities", entity.AllowedQuantities); _writer.Write("DisableBuyButton", entity.DisableBuyButton.ToString()); _writer.Write("DisableWishlistButton", entity.DisableWishlistButton.ToString()); @@ -562,6 +564,7 @@ public void WriteProduct(dynamic product, string node) _writer.Write("BasePriceInfo", (string)product._BasePriceInfo); _writer.Write("VisibleIndividually", entity.VisibleIndividually.ToString()); _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); + _writer.Write("IsSystemProduct", entity.IsSystemProduct.ToString()); _writer.Write("BundleTitleText", entity.BundleTitleText); _writer.Write("BundlePerItemPricing", entity.BundlePerItemPricing.ToString()); _writer.Write("BundlePerItemShipping", entity.BundlePerItemShipping.ToString()); @@ -603,6 +606,35 @@ public void WriteProduct(dynamic product, string node) _writer.WriteEndElement(); // AppliedDiscounts } + if (product.Downloads != null) + { + _writer.WriteStartElement("Downloads"); + foreach (dynamic download in product.Downloads) + { + Download downloadEntity = download.Entity; + + _writer.WriteStartElement("Download"); + _writer.Write("Id", downloadEntity.Id.ToString()); + _writer.Write("DownloadGuid", downloadEntity.DownloadGuid.ToString()); + _writer.Write("UseDownloadUrl", downloadEntity.UseDownloadUrl.ToString()); + _writer.Write("DownloadUrl", downloadEntity.DownloadUrl); + _writer.Write("ContentType", downloadEntity.ContentType); + _writer.Write("Filename", downloadEntity.Filename); + _writer.Write("Extension", downloadEntity.Extension); + _writer.Write("IsNew", downloadEntity.IsNew.ToString()); + _writer.Write("IsTransient", downloadEntity.IsTransient.ToString()); + _writer.Write("UpdatedOnUtc", downloadEntity.UpdatedOnUtc.ToString(_culture)); + _writer.Write("MediaStorageId", downloadEntity.MediaStorageId.HasValue ? downloadEntity.MediaStorageId.Value.ToString() : ""); + _writer.Write("EntityId", downloadEntity.EntityId.ToString()); + _writer.Write("EntityName", downloadEntity.EntityName); + _writer.Write("FileVersion", downloadEntity.FileVersion); + _writer.Write("Changelog", downloadEntity.Changelog); + + _writer.WriteEndElement(); // Download + } + _writer.WriteEndElement(); // Downloads + } + if (product.TierPrices != null) { _writer.WriteStartElement("TierPrices"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs index 1cdfb837be..be7584b70f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Discounts; @@ -9,22 +8,24 @@ namespace SmartStore.Services.DataExchange.Export { - /// - /// Cargo data to reduce database round trips during work with product batches (export, list model creation etc.) - /// - public class ProductExportContext : PriceCalculationContext + /// + /// Cargo data to reduce database round trips during work with product batches (export, list model creation etc.) + /// + public class ProductExportContext : PriceCalculationContext { private Func> _funcProductPictures; private Func> _funcProductTags; private Func> _funcSpecificationAttributes; private Func> _funcPictures; + private Func> _funcDownloads; - private LazyMultimap _productPictures; + private LazyMultimap _productPictures; private LazyMultimap _productTags; private LazyMultimap _specificationAttributes; private LazyMultimap _pictures; + private LazyMultimap _downloads; - public ProductExportContext( + public ProductExportContext( IEnumerable products, Func> attributes, Func> attributeCombinations, @@ -36,7 +37,8 @@ public ProductExportContext( Func> productBundleItems, Func> pictures, Func> productPictures, - Func> productTags) + Func> productTags, + Func> downloads) : base(products, attributes, attributeCombinations, @@ -50,18 +52,31 @@ public ProductExportContext( _funcProductPictures = productPictures; _funcProductTags = productTags; _funcSpecificationAttributes = specificationAttributes; + _funcDownloads = downloads; } public new void Clear() { - if (_productPictures != null) - _productPictures.Clear(); - if (_productTags != null) - _productTags.Clear(); - if (_specificationAttributes != null) - _specificationAttributes.Clear(); - if (_pictures != null) - _pictures.Clear(); + if (_productPictures != null) + { + _productPictures.Clear(); + } + if (_productTags != null) + { + _productTags.Clear(); + } + if (_specificationAttributes != null) + { + _specificationAttributes.Clear(); + } + if (_pictures != null) + { + _pictures.Clear(); + } + if (_downloads != null) + { + _downloads.Clear(); + } base.Clear(); } @@ -113,5 +128,17 @@ public LazyMultimap SpecificationAttributes return _specificationAttributes; } } + + public LazyMultimap Downloads + { + get + { + if (_downloads == null) + { + _downloads = new LazyMultimap(keys => _funcDownloads(keys), _productIds); + } + return _downloads; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs index a25235fe00..541850340f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs @@ -18,7 +18,7 @@ public DataImportTask( public void Execute(TaskExecutionContext ctx) { - var profileId = ctx.ScheduleTask.Alias.ToInt(); + var profileId = ctx.ScheduleTaskHistory.ScheduleTask.Alias.ToInt(); var profile = _importProfileService.GetImportProfileById(profileId); var request = new DataImportRequest(profile); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 61c9179d0e..3ad203cc4a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -100,40 +100,35 @@ public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoN item.MimeType = MediaTypeNames.Image.Jpeg; } - var extension = MimeTypes.MapMimeTypeToExtension(item.MimeType); - - if (extension.HasValue()) + if (urlOrPath.IsWebUrl()) { - if (urlOrPath.IsWebUrl()) - { - item.Url = urlOrPath; - item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); + item.Url = urlOrPath; + item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); - if (DownloadedItems.ContainsKey(urlOrPath)) - { - item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); - item.Success = true; - } - else - { - item.Path = Path.Combine(ImageDownloadFolder, item.FileName + extension.EnsureStartsWith(".")); - } - } - else if (Path.IsPathRooted(urlOrPath)) + if (DownloadedItems.ContainsKey(urlOrPath)) { - item.Path = urlOrPath; + item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); item.Success = true; } else { - item.Path = Path.Combine(ImageFolder, urlOrPath); - item.Success = true; - } + var extension = MimeTypes.MapMimeTypeToExtension(item.MimeType).NullEmpty() ?? ".jpg"; - return item; + item.Path = Path.Combine(ImageDownloadFolder, item.FileName + extension.EnsureStartsWith(".")); + } + } + else if (Path.IsPathRooted(urlOrPath)) + { + item.Path = urlOrPath; + item.Success = true; + } + else + { + item.Path = Path.Combine(ImageFolder, urlOrPath); + item.Success = true; } - return null; + return item; } public void Succeeded(FileDownloadManagerItem item) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 1f213ee9e4..a709f5288e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -9,6 +9,7 @@ namespace SmartStore.Services.DataExchange.Import public class ImportRow where T : BaseEntity { private const string ExplicitNull = "[NULL]"; + private const string ExplicitIgnore = "[IGNORE]"; private bool _initialized = false; private T _entity; @@ -193,7 +194,7 @@ public bool TryGetDataValue(string columnName, string index, out TProp va } object rawValue; - if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) + if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value && !rawValue.ToString().IsCaseInsensitiveEqual(ExplicitIgnore)) { value = rawValue.ToString().IsCaseInsensitiveEqual(ExplicitNull) ? default(TProp) @@ -252,7 +253,7 @@ public bool SetProperty( { // explicitly ignore this property } - else if (_row.TryGetValue(mapping.MappedName, out value) && (value != null && value != DBNull.Value)) + else if (_row.TryGetValue(mapping.MappedName, out value) && value != null && value != DBNull.Value && !value.ToString().IsCaseInsensitiveEqual(ExplicitIgnore)) { // source contains field value. Set it. TProp converted; diff --git a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs index 0085240ff5..d417801b19 100644 --- a/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs +++ b/src/Libraries/SmartStore.Services/Directory/CurrencyService.cs @@ -31,13 +31,13 @@ public CurrencyService( IProviderManager providerManager, IStoreContext storeContext) { - this._currencyRepository = currencyRepository; - this._storeMappingService = storeMappingService; - this._currencySettings = currencySettings; - this._pluginFinder = pluginFinder; - this._eventPublisher = eventPublisher; - this._providerManager = providerManager; - this._storeContext = storeContext; + _currencyRepository = currencyRepository; + _storeMappingService = storeMappingService; + _currencySettings = currencySettings; + _pluginFinder = pluginFinder; + _eventPublisher = eventPublisher; + _providerManager = providerManager; + _storeContext = storeContext; } public virtual IList GetCurrencyLiveRates(string exchangeRateCurrencyCode) @@ -52,8 +52,7 @@ public virtual IList GetCurrencyLiveRates(string exchangeRateCurre public virtual void DeleteCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); + Guard.NotNull(currency, nameof(currency)); _currencyRepository.Delete(currency); } @@ -68,9 +67,9 @@ public virtual Currency GetCurrencyById(int currencyId) public virtual Currency GetCurrencyByCode(string currencyCode) { - if (String.IsNullOrEmpty(currencyCode)) - return null; - return GetAllCurrencies(true).FirstOrDefault(c => c.CurrencyCode.ToLower() == currencyCode.ToLower()); + Guard.NotNull(currencyCode, nameof(currencyCode)); + + return GetAllCurrencies(true).FirstOrDefault(c => c.CurrencyCode.ToLower() == currencyCode.ToLower()); } public virtual IList GetAllCurrencies(bool showHidden = false, int storeId = 0) @@ -96,24 +95,23 @@ public virtual IList GetAllCurrencies(bool showHidden = false, int sto public virtual void InsertCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); + Guard.NotNull(currency, nameof(currency)); - _currencyRepository.Insert(currency); + _currencyRepository.Insert(currency); } public virtual void UpdateCurrency(Currency currency) { - if (currency == null) - throw new ArgumentNullException("currency"); + Guard.NotNull(currency, nameof(currency)); - _currencyRepository.Update(currency); + _currencyRepository.Update(currency); } public virtual decimal ConvertCurrency(decimal amount, decimal exchangeRate) { if (amount != decimal.Zero && exchangeRate != decimal.Zero) return amount * exchangeRate; + return decimal.Zero; } @@ -122,11 +120,13 @@ public virtual decimal ConvertCurrency(decimal amount, Currency sourceCurrency, decimal result = amount; if (sourceCurrency.Id == targetCurrency.Id) return result; + if (result != decimal.Zero && sourceCurrency.Id != targetCurrency.Id) { result = ConvertToPrimaryExchangeRateCurrency(result, sourceCurrency, store); result = ConvertFromPrimaryExchangeRateCurrency(result, targetCurrency, store); } + return result; } diff --git a/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs b/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs index 3923083fdb..2b3ba237fa 100644 --- a/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs +++ b/src/Libraries/SmartStore.Services/Directory/DeliveryTimeService.cs @@ -8,11 +8,12 @@ using SmartStore.Core.Localization; using SmartStore.Core.Plugins; using SmartStore.Data.Caching; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; namespace SmartStore.Services.Directory { - public partial class DeliveryTimeService : IDeliveryTimeService + public partial class DeliveryTimeService : IDeliveryTimeService { private readonly IRepository _deliveryTimeRepository; private readonly IRepository _productRepository; @@ -85,18 +86,10 @@ public virtual DeliveryTime GetDeliveryTimeById(int deliveryTimeId) return _deliveryTimeRepository.GetByIdCached(deliveryTimeId, "deliverytime-{0}".FormatInvariant(deliveryTimeId)); } - public virtual DeliveryTime GetDeliveryTime(Product product) + public virtual DeliveryTime GetDeliveryTime(Product product) { - if (product == null) - return null; - - if ((product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) - && _catalogSettings.DeliveryTimeIdForEmptyStock.HasValue && product.StockQuantity <= 0) - { - return GetDeliveryTimeById(_catalogSettings.DeliveryTimeIdForEmptyStock.Value); - } - - return GetDeliveryTimeById(product.DeliveryTimeId ?? 0); + var deliveryTimeId = product.GetDeliveryTimeIdAccordingToStock(_catalogSettings); + return GetDeliveryTimeById(deliveryTimeId ?? 0); } public virtual IList GetAllDeliveryTimes() diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs index ccc62d5921..4674dc7850 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs @@ -1,10 +1,9 @@ -using System; using System.Collections.Generic; using SmartStore.Core.Domain.Discounts; namespace SmartStore.Services.Discounts { - public static class DiscountExtentions + public static class DiscountExtentions { /// /// Gets the discount amount for the specified value @@ -14,34 +13,44 @@ public static class DiscountExtentions /// The discount amount public static decimal GetDiscountAmount(this Discount discount, decimal amount) { - if (discount == null) - throw new ArgumentNullException("discount"); + Guard.NotNull(discount, nameof(discount)); + var result = decimal.Zero; - decimal result = decimal.Zero; - if (discount.UsePercentage) - result = (decimal)((((float)amount) * ((float)discount.DiscountPercentage)) / 100f); - else - result = discount.DiscountAmount; - - if (result < decimal.Zero) - result = decimal.Zero; + if (discount.UsePercentage) + { + result = (decimal)((((float)amount) * ((float)discount.DiscountPercentage)) / 100f); + } + else + { + result = discount.DiscountAmount; + } return result; } - - public static Discount GetPreferredDiscount(this IList discounts, decimal amount) + + /// + /// Get the discount that achieves the highest discount amount other than zero. + /// + /// List of discounts + /// Amount without discount (for percentage discounts) + /// Discount that achieves the highest discount amount other than zero. + public static Discount GetPreferredDiscount(this IList discounts, decimal amount) { Discount preferredDiscount = null; - decimal maximumDiscountValue = decimal.Zero; + decimal? maximumDiscountValue = null; + foreach (var discount in discounts) { - decimal currentDiscountValue = discount.GetDiscountAmount(amount); - if (currentDiscountValue > maximumDiscountValue) - { - maximumDiscountValue = currentDiscountValue; - preferredDiscount = discount; - } + var currentDiscountValue = discount.GetDiscountAmount(amount); + if (currentDiscountValue != decimal.Zero) + { + if (!maximumDiscountValue.HasValue || currentDiscountValue > maximumDiscountValue) + { + maximumDiscountValue = currentDiscountValue; + preferredDiscount = discount; + } + } } return preferredDiscount; diff --git a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs index cafd22688c..d6d4fe8b19 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs @@ -154,7 +154,7 @@ public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService f if (forumTopic == null) throw new ArgumentNullException("forumTopic"); - var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, 0, 1); + var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, true, 0, 1); if (forumPosts.Count > 0) return forumPosts[0]; diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 2fe46d227c..ae0c3c1172 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; using SmartStore.Data.Caching; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Messages; namespace SmartStore.Services.Forums { @@ -61,243 +58,151 @@ public ForumService( private void UpdateForumStats(int forumId) { - if (forumId == 0) - { - return; - } var forum = GetForumById(forumId); if (forum == null) { return; } - //number of topics - var queryNumTopics = from ft in _forumTopicRepository.Table - where ft.ForumId == forumId - select ft.Id; - int numTopics = queryNumTopics.Count(); - - //number of posts - var queryNumPosts = from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId - select fp.Id; - int numPosts = queryNumPosts.Count(); - - //last values - int lastTopicId = 0; - int lastPostId = 0; - int lastPostCustomerId = 0; - DateTime? lastPostTime = null; - var queryLastValues = from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where ft.ForumId == forumId - orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending - select new - { - LastTopicId = ft.Id, - LastPostId = fp.Id, - LastPostCustomerId = fp.CustomerId, - LastPostTime = fp.CreatedOnUtc - }; + var queryLastValues = + from ft in _forumTopicRepository.TableUntracked + join fp in _forumPostRepository.TableUntracked on ft.Id equals fp.TopicId + where ft.ForumId == forumId + orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending + select new + { + LastTopicId = ft.Id, + LastPostId = fp.Id, + LastPostCustomerId = fp.CustomerId, + LastPostTime = fp.CreatedOnUtc + }; var lastValues = queryLastValues.FirstOrDefault(); - if (lastValues != null) - { - lastTopicId = lastValues.LastTopicId; - lastPostId = lastValues.LastPostId; - lastPostCustomerId = lastValues.LastPostCustomerId; - lastPostTime = lastValues.LastPostTime; - } - //update forum - forum.NumTopics = numTopics; - forum.NumPosts = numPosts; - forum.LastTopicId = lastTopicId; - forum.LastPostId = lastPostId; - forum.LastPostCustomerId = lastPostCustomerId; - forum.LastPostTime = lastPostTime; + forum.LastTopicId = lastValues?.LastTopicId ?? 0; + forum.LastPostId = lastValues?.LastPostId ?? 0; + forum.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + forum.LastPostTime = lastValues?.LastPostTime; + forum.NumTopics = _forumTopicRepository.Table.Where(x => x.ForumId == forumId).Count(); + forum.NumPosts = ( + from ft in _forumTopicRepository.Table + join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId + where ft.ForumId == forumId + select fp.Id).Count(); + UpdateForum(forum); } private void UpdateForumTopicStats(int forumTopicId) { - if (forumTopicId == 0) - { - return; - } var forumTopic = GetTopicById(forumTopicId); if (forumTopic == null) { return; } - //number of posts - var queryNumPosts = from fp in _forumPostRepository.Table - where fp.TopicId == forumTopicId - select fp.Id; - int numPosts = queryNumPosts.Count(); - - //last values - int lastPostId = 0; - int lastPostCustomerId = 0; - DateTime? lastPostTime = null; - var queryLastValues = from fp in _forumPostRepository.Table - where fp.TopicId == forumTopicId - orderby fp.CreatedOnUtc descending - select new - { - LastPostId = fp.Id, - LastPostCustomerId = fp.CustomerId, - LastPostTime = fp.CreatedOnUtc - }; + var queryLastValues = + from fp in _forumPostRepository.TableUntracked + where fp.TopicId == forumTopicId + orderby fp.CreatedOnUtc descending + select new + { + LastPostId = fp.Id, + LastPostCustomerId = fp.CustomerId, + LastPostTime = fp.CreatedOnUtc + }; var lastValues = queryLastValues.FirstOrDefault(); - if (lastValues != null) - { - lastPostId = lastValues.LastPostId; - lastPostCustomerId = lastValues.LastPostCustomerId; - lastPostTime = lastValues.LastPostTime; - } - //update topic - forumTopic.NumPosts = numPosts; - forumTopic.LastPostId = lastPostId; - forumTopic.LastPostCustomerId = lastPostCustomerId; - forumTopic.LastPostTime = lastPostTime; + forumTopic.LastPostId = lastValues?.LastPostId ?? 0; + forumTopic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + forumTopic.LastPostTime = lastValues?.LastPostTime; + forumTopic.NumPosts = _forumPostRepository.Table.Where(x => x.TopicId == forumTopicId).Count(); + UpdateTopic(forumTopic); } private void UpdateCustomerStats(int customerId) { - if (customerId == 0) - { - return; - } - - var customer = _customerService.GetCustomerById(customerId); - - if (customer == null) + if (customerId != 0) { - return; - } - - var query = from fp in _forumPostRepository.Table - where fp.CustomerId == customerId - select fp.Id; - int numPosts = query.Count(); - - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); - } + var customer = _customerService.GetCustomerById(customerId); + if (customer != null) + { + var numPosts = _forumPostRepository.Table.Where(x => x.CustomerId == customerId).Count(); - private bool IsForumModerator(Customer customer) - { - if (customer.IsForumModerator()) - { - return true; + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); + } } - return false; } - public virtual void DeleteForumGroup(ForumGroup forumGroup) - { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } - - _forumGroupRepository.Delete(forumGroup); - } + #region Group public virtual ForumGroup GetForumGroupById(int forumGroupId) { if (forumGroupId == 0) + { return null; + } return _forumGroupRepository.GetById(forumGroupId); } - public virtual IList GetAllForumGroups(bool showHidden = false) + public virtual IList GetAllForumGroups(int storeId = 0) { - var query = _forumGroupRepository.Table; - - if (!showHidden && !QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; + var query = _forumGroupRepository.Table.Expand(x => x.Forums); - query = - from fg in query - join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm - from sm in fg_sm.DefaultIfEmpty() - where !fg.LimitedToStores || currentStoreId == sm.StoreId - select fg; + if (!QuerySettings.IgnoreMultiStore && storeId > 0) + { + query = + from fg in query + join sm in _storeMappingRepository.Table on new { c1 = fg.Id, c2 = "ForumGroup" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into fg_sm + from sm in fg_sm.DefaultIfEmpty() + where !fg.LimitedToStores || storeId == sm.StoreId + select fg; - query = - from fg in query - group fg by fg.Id into fgGroup - orderby fgGroup.Key - select fgGroup.FirstOrDefault(); - } + query = + from fg in query + group fg by fg.Id into fgGroup + orderby fgGroup.Key + select fgGroup.FirstOrDefault(); + } - query = query.OrderBy(m => m.DisplayOrder); + query = query.OrderBy(x => x.DisplayOrder); - return query.ToListCached(); - } + return query.ToListCached(); + } public virtual void InsertForumGroup(ForumGroup forumGroup) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forumGroup, nameof(forumGroup)); _forumGroupRepository.Insert(forumGroup); } public virtual void UpdateForumGroup(ForumGroup forumGroup) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forumGroup, nameof(forumGroup)); _forumGroupRepository.Update(forumGroup); } - public virtual void DeleteForum(Forum forum) - { - if (forum == null) - { - throw new ArgumentNullException("forum"); - } - - //delete forum subscriptions (topics) - var queryTopicIds = from ft in _forumTopicRepository.Table - where ft.ForumId == forum.Id - select ft.Id; - var queryFs1 = from fs in _forumSubscriptionRepository.Table - where queryTopicIds.Contains(fs.TopicId) - select fs; - foreach (var fs in queryFs1.ToList()) + public virtual void DeleteForumGroup(ForumGroup forumGroup) + { + if (forumGroup != null) { - _forumSubscriptionRepository.Delete(fs); - } + _forumGroupRepository.Delete(forumGroup); + } + } - //delete forum subscriptions (forum) - var queryFs2 = from fs in _forumSubscriptionRepository.Table - where fs.ForumId == forum.Id - select fs; - foreach (var fs2 in queryFs2.ToList()) - { - _forumSubscriptionRepository.Delete(fs2); - } + #endregion - //delete forum - _forumRepository.Delete(forum); - } + #region Forum public virtual Forum GetForumById(int forumId) { if (forumId == 0) + { return null; + } return _forumRepository.GetById(forumId); } @@ -315,111 +220,92 @@ orderby f.DisplayOrder public virtual void InsertForum(Forum forum) { - if (forum == null) - { - throw new ArgumentNullException("forum"); - } + Guard.NotNull(forum, nameof(forum)); _forumRepository.Insert(forum); } public virtual void UpdateForum(Forum forum) { - if (forum == null) - { - throw new ArgumentNullException("forum"); - } + Guard.NotNull(forum, nameof(forum)); - _forumRepository.Update(forum); + _forumRepository.Update(forum); } - public virtual void DeleteTopic(ForumTopic forumTopic) - { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } - - int customerId = forumTopic.CustomerId; - int forumId = forumTopic.ForumId; - - //delete topic - _forumTopicRepository.Delete(forumTopic); + public virtual void DeleteForum(Forum forum) + { + if (forum == null) + { + return; + } - //delete forum subscriptions - var queryFs = from ft in _forumSubscriptionRepository.Table - where ft.TopicId == forumTopic.Id - select ft; - var forumSubscriptions = queryFs.ToList(); - foreach (var fs in forumSubscriptions) + // Delete forum subscriptions (topics). + var queryTopicIds = from ft in _forumTopicRepository.Table + where ft.ForumId == forum.Id + select ft.Id; + var queryFs1 = from fs in _forumSubscriptionRepository.Table + where queryTopicIds.Contains(fs.TopicId) + select fs; + foreach (var fs in queryFs1.ToList()) { _forumSubscriptionRepository.Delete(fs); } - //update stats - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); - } + // Delete forum subscriptions (forum). + var queryFs2 = from fs in _forumSubscriptionRepository.Table + where fs.ForumId == forum.Id + select fs; + foreach (var fs2 in queryFs2.ToList()) + { + _forumSubscriptionRepository.Delete(fs2); + } - public virtual ForumTopic GetTopicById(int forumTopicId) - { - return GetTopicById(forumTopicId, false); + // Delete forum. + _forumRepository.Delete(forum); } - public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) + #endregion + + #region Topic + + public virtual ForumTopic GetTopicById(int forumTopicId) { if (forumTopicId == 0) { return null; } - var query = from ft in _forumTopicRepository.Table - where ft.Id == forumTopicId - select ft; - var forumTopic = query.SingleOrDefault(); - if (forumTopic == null) - { - return null; - } + var entity = _forumTopicRepository.Table.FirstOrDefault(x => x.Id == forumTopicId); + return entity; + } - if (increaseViews) + public virtual IList GetTopicsByIds(int[] topicIds) + { + if (topicIds == null || topicIds.Length == 0) { - forumTopic.Views = ++forumTopic.Views; - UpdateTopic(forumTopic); + return new List(); } - return forumTopic; + var query = _forumTopicRepository.Table + .Where(x => topicIds.Contains(x.Id)); + + return query.OrderBySequence(topicIds).ToList(); } - public virtual IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize) + public virtual IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize) { - DateTime? limitDate = null; - - if (limitDays > 0) + var query = _forumTopicRepository.TableUntracked; + if (forumId != 0) { - limitDate = DateTime.UtcNow.AddDays(-limitDays); + query = query.Where(x => x.ForumId == forumId); } - var query1 = - from ft in _forumTopicRepository.Table - join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId - where - (forumId == 0 || ft.ForumId == forumId) && - (customerId == 0 || ft.CustomerId == customerId) && - (!limitDate.HasValue || limitDate.Value <= ft.LastPostTime) && - ( - ((searchType == ForumSearchType.All || searchType == ForumSearchType.TopicTitlesOnly) && ft.Subject.Contains(keywords)) || - ((searchType == ForumSearchType.All || searchType == ForumSearchType.PostTextOnly) && fp.Text.Contains(keywords)) - ) - select ft.Id; - - var query2 = - from ft in _forumTopicRepository.Table - where query1.Contains(ft.Id) - orderby ft.TopicTypeId descending, ft.LastPostTime descending, ft.Id descending - select ft; + query = query + .OrderByDescending(x => x.TopicTypeId) + .ThenByDescending(x => x.LastPostTime) + .ThenByDescending(x => x.Id); - var topics = new PagedList(query2, pageIndex, pageSize); + var topics = new PagedList(query, pageIndex, pageSize); return topics; } @@ -458,17 +344,12 @@ orderby ftGroup.Key public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } + Guard.NotNull(forumTopic, nameof(forumTopic)); _forumTopicRepository.Insert(forumTopic); - //update stats UpdateForumStats(forumTopic.ForumId); - //send notifications if (sendNotifications) { var forum = forumTopic.Forum; @@ -482,7 +363,7 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, forumTopic, languageId); } @@ -491,80 +372,85 @@ public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) } public virtual void UpdateTopic(ForumTopic forumTopic) + { + Guard.NotNull(forumTopic, nameof(forumTopic)); + + _forumTopicRepository.Update(forumTopic); + } + + public virtual void DeleteTopic(ForumTopic forumTopic) { if (forumTopic == null) { - throw new ArgumentNullException("forumTopic"); + return; } - _forumTopicRepository.Update(forumTopic); + int customerId = forumTopic.CustomerId; + int forumId = forumTopic.ForumId; + + _forumTopicRepository.Delete(forumTopic); + + // Delete forum subscriptions. + var queryFs = from ft in _forumSubscriptionRepository.Table + where ft.TopicId == forumTopic.Id + select ft; + var forumSubscriptions = queryFs.ToList(); + foreach (var fs in forumSubscriptions) + { + _forumSubscriptionRepository.Delete(fs); + } + + UpdateForumStats(forumId); + UpdateCustomerStats(customerId); } public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) { var forumTopic = GetTopicById(forumTopicId); if (forumTopic == null) + { return forumTopic; + } - if (this.IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) + if (IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) { - int previousForumId = forumTopic.ForumId; + var previousForumId = forumTopic.ForumId; var newForum = GetForumById(newForumId); - - if (newForum != null) + if (newForum != null && previousForumId != newForumId) { - if (previousForumId != newForumId) - { - forumTopic.ForumId = newForum.Id; - UpdateTopic(forumTopic); + forumTopic.ForumId = newForum.Id; + UpdateTopic(forumTopic); - //update forum stats - UpdateForumStats(previousForumId); - UpdateForumStats(newForumId); - } + UpdateForumStats(previousForumId); + UpdateForumStats(newForumId); } } + return forumTopic; } - public virtual void DeletePost(ForumPost forumPost) - { - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } - - int forumTopicId = forumPost.TopicId; - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumTopicId); - int forumId = forumTopic.ForumId; + public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) + { + var pageIndex = 0; + var forumPosts = GetAllPosts(forumTopicId, 0, true, 0, int.MaxValue); - //delete topic if it was the first post - bool deleteTopic = false; - var firstPost = forumTopic.GetFirstPost(this); - if (firstPost != null && firstPost.Id == forumPost.Id) + for (var i = 0; i < forumPosts.TotalCount; i++) { - deleteTopic = true; + if (forumPosts[i].Id == postId) + { + if (pageSize > 0) + { + pageIndex = i / pageSize; + } + } } - //delete forum post - _forumPostRepository.Delete(forumPost); - - //delete topic - if (deleteTopic) - { - DeleteTopic(forumTopic); - } + return pageIndex; + } - //update stats - if (!deleteTopic) - { - UpdateForumTopicStats(forumTopicId); - } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + #endregion - } + #region Post public virtual ForumPost GetPostById(int forumPostId) { @@ -573,20 +459,24 @@ public virtual ForumPost GetPostById(int forumPostId) return null; } - var query = from fp in _forumPostRepository.Table - where fp.Id == forumPostId - select fp; - var forumPost = query.SingleOrDefault(); - + var forumPost = _forumPostRepository.Table.FirstOrDefault(x => x.Id == forumPostId); return forumPost; } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, int pageIndex, int pageSize) + public virtual IList GetPostsByIds(int[] postIds) { - return GetAllPosts(forumTopicId, customerId, keywords, true, pageIndex, pageSize); + if (postIds == null || postIds.Length == 0) + { + return new List(); + } + + var query = _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic).Expand(x => x.Customer) + .Where(x => postIds.Contains(x.Id)); + + return query.OrderBySequence(postIds).ToList(); } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) + public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize) { var query = _forumPostRepository.Table; if (forumTopicId > 0) @@ -597,10 +487,6 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI { query = query.Where(fp => customerId == fp.CustomerId); } - if (!String.IsNullOrEmpty(keywords)) - { - query = query.Where(fp => fp.Text.Contains(keywords)); - } if (ascSort) { query = query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); @@ -611,45 +497,39 @@ public virtual IPagedList GetAllPosts(int forumTopicId, int customerI } var forumPosts = new PagedList(query, pageIndex, pageSize); - return forumPosts; } public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) { - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } + Guard.NotNull(forumPost, nameof(forumPost)); _forumPostRepository.Insert(forumPost); - //update stats - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumPost.TopicId); - int forumId = forumTopic.ForumId; + var forumTopic = GetTopicById(forumPost.TopicId); + UpdateForumTopicStats(forumPost.TopicId); - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + UpdateForumStats(forumTopic.ForumId); + UpdateCustomerStats(forumPost.CustomerId); - //notifications if (sendNotifications) { var forum = forumTopic.Forum; - var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); - var languageId = _services.WorkContext.WorkingLanguage.Id; + var subscriptions = GetAllSubscriptions(0, 0, forumTopic.Id, 0, int.MaxValue); + var friendlyTopicPageIndex = CalculateTopicPageIndex( + forumPost.TopicId, + _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, + forumPost.Id) + 1; - int friendlyTopicPageIndex = CalculateTopicPageIndex(forumPost.TopicId, _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10, forumPost.Id) + 1; - - foreach (ForumSubscription subscription in subscriptions) + foreach (var subscription in subscriptions) { if (subscription.CustomerId == forumPost.CustomerId) { continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, forumPost, friendlyTopicPageIndex, languageId); } @@ -659,25 +539,52 @@ public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) public virtual void UpdatePost(ForumPost forumPost) { - //validation - if (forumPost == null) - { - throw new ArgumentNullException("forumPost"); - } + Guard.NotNull(forumPost, nameof(forumPost)); _forumPostRepository.Update(forumPost); } - public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + public virtual void DeletePost(ForumPost forumPost) { - if (privateMessage == null) + if (forumPost == null) { - throw new ArgumentNullException("privateMessage"); + return; + } + + var forumTopicId = forumPost.TopicId; + var customerId = forumPost.CustomerId; + var forumTopic = GetTopicById(forumTopicId); + var forumId = forumTopic.ForumId; + + // Delete topic if it was the first post. + var deleteTopic = false; + var firstPost = forumTopic.GetFirstPost(this); + if (firstPost != null && firstPost.Id == forumPost.Id) + { + deleteTopic = true; + } + + // Delete forum post. + _forumPostRepository.Delete(forumPost); + + // Delete topic. + if (deleteTopic) + { + DeleteTopic(forumTopic); } - _forumPrivateMessageRepository.Delete(privateMessage); + if (!deleteTopic) + { + UpdateForumTopicStats(forumTopicId); + } + UpdateForumStats(forumId); + UpdateCustomerStats(customerId); } + #endregion + + #region Private message + public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) { if (privateMessageId == 0) @@ -685,11 +592,7 @@ public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) return null; } - var query = from pm in _forumPrivateMessageRepository.Table - where pm.Id == privateMessageId - select pm; - var privateMessage = query.SingleOrDefault(); - + var privateMessage = _forumPrivateMessageRepository.Table.FirstOrDefault(x => x.Id == privateMessageId); return privateMessage; } @@ -700,44 +603,45 @@ public virtual IPagedList GetAllPrivateMessages( bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, - string keywords, int pageIndex, int pageSize) { var query = _forumPrivateMessageRepository.Table; - if (storeId > 0) - query = query.Where(pm => storeId == pm.StoreId); - if (fromCustomerId > 0) - query = query.Where(pm => fromCustomerId == pm.FromCustomerId); - if (toCustomerId > 0) - query = query.Where(pm => toCustomerId == pm.ToCustomerId); - if (isRead.HasValue) - query = query.Where(pm => isRead.Value == pm.IsRead); - if (isDeletedByAuthor.HasValue) - query = query.Where(pm => isDeletedByAuthor.Value == pm.IsDeletedByAuthor); - if (isDeletedByRecipient.HasValue) - query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); - - if (!String.IsNullOrEmpty(keywords)) - { - query = query.Where(pm => pm.Subject.Contains(keywords)); - query = query.Where(pm => pm.Text.Contains(keywords)); - } + if (storeId > 0) + { + query = query.Where(pm => storeId == pm.StoreId); + } + if (fromCustomerId > 0) + { + query = query.Where(pm => fromCustomerId == pm.FromCustomerId); + } + if (toCustomerId > 0) + { + query = query.Where(pm => toCustomerId == pm.ToCustomerId); + } + if (isRead.HasValue) + { + query = query.Where(pm => isRead.Value == pm.IsRead); + } + if (isDeletedByAuthor.HasValue) + { + query = query.Where(pm => isDeletedByAuthor.Value == pm.IsDeletedByAuthor); + } + if (isDeletedByRecipient.HasValue) + { + query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient); + } query = query.OrderByDescending(pm => pm.CreatedOnUtc); var privateMessages = new PagedList(query, pageIndex, pageSize); - return privateMessages; } public virtual void InsertPrivateMessage(PrivateMessage privateMessage) { - if (privateMessage == null) - { - throw new ArgumentNullException("privateMessage"); - } + Guard.NotNull(privateMessage, nameof(privateMessage)); _forumPrivateMessageRepository.Insert(privateMessage); @@ -747,10 +651,8 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) throw new SmartException("Recipient could not be loaded"); } - //UI notification _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, privateMessage.StoreId); - //Email notification if (_forumSettings.NotifyAboutPrivateMessages) { _services.MessageFactory.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); @@ -759,8 +661,7 @@ public virtual void InsertPrivateMessage(PrivateMessage privateMessage) public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) { - if (privateMessage == null) - throw new ArgumentNullException("privateMessage"); + Guard.NotNull(privateMessage, nameof(privateMessage)); if (privateMessage.IsDeletedByAuthor && privateMessage.IsDeletedByRecipient) { @@ -772,16 +673,18 @@ public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) } } - public virtual void DeleteSubscription(ForumSubscription forumSubscription) + public virtual void DeletePrivateMessage(PrivateMessage privateMessage) { - if (forumSubscription == null) + if (privateMessage != null) { - throw new ArgumentNullException("forumSubscription"); - } - - _forumSubscriptionRepository.Delete(forumSubscription); + _forumPrivateMessageRepository.Delete(privateMessage); + } } + #endregion + + #region Subscription + public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) { if (forumSubscriptionId == 0) @@ -789,11 +692,7 @@ public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) return null; } - var query = from fs in _forumSubscriptionRepository.Table - where fs.Id == forumSubscriptionId - select fs; - var forumSubscription = query.SingleOrDefault(); - + var forumSubscription = _forumSubscriptionRepository.Table.FirstOrDefault(x => x.Id == forumSubscriptionId); return forumSubscription; } @@ -821,32 +720,33 @@ where fsQuery.Contains(fs.SubscriptionGuid) public virtual void InsertSubscription(ForumSubscription forumSubscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } + Guard.NotNull(forumSubscription, nameof(forumSubscription)); _forumSubscriptionRepository.Insert(forumSubscription); } public virtual void UpdateSubscription(ForumSubscription forumSubscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } - + Guard.NotNull(forumSubscription, nameof(forumSubscription)); + _forumSubscriptionRepository.Update(forumSubscription); } - public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) + public virtual void DeleteSubscription(ForumSubscription forumSubscription) { - if (forum == null) + if (forumSubscription != null) { - return false; + _forumSubscriptionRepository.Delete(forumSubscription); } + } + + #endregion - if (customer == null) + #region Customer + + public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) + { + if (forum == null || customer == null) { return false; } @@ -856,7 +756,7 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -866,29 +766,19 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) + if (topic == null || customer == null || customer.IsGuest()) { return false; } - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToEditPosts) { - bool ownTopic = customer.Id == topic.CustomerId; + var ownTopic = customer.Id == topic.CustomerId; return ownTopic; } @@ -897,12 +787,7 @@ public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) + if (topic == null || customer == null) { return false; } @@ -912,7 +797,7 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -922,29 +807,19 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic topic) { - if (topic == null) + if (topic == null || customer == null || customer.IsGuest()) { return false; } - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToDeletePosts) { - bool ownTopic = customer.Id == topic.CustomerId; + var ownTopic = customer.Id == topic.CustomerId; return ownTopic; } @@ -953,12 +828,7 @@ public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) + if (topic == null || customer == null) { return false; } @@ -973,29 +843,19 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost post) { - if (post == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (post == null || customer == null || customer.IsGuest()) { return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToEditPosts) { - bool ownPost = customer.Id == post.CustomerId; + var ownPost = customer.Id == post.CustomerId; return ownPost; } @@ -1004,29 +864,19 @@ public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost pos public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post) { - if (post == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (post == null || customer == null || customer.IsGuest()) { return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } if (_forumSettings.AllowCustomersToDeletePosts) { - bool ownPost = customer.Id == post.CustomerId; + var ownPost = customer.Id == post.CustomerId; return ownPost; } @@ -1035,17 +885,12 @@ public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost p public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) { - if (customer == null) + if (customer == null || customer.IsGuest()) { return false; } - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -1055,12 +900,7 @@ public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) public virtual bool IsCustomerAllowedToSubscribe(Customer customer) { - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer == null || customer.IsGuest()) { return false; } @@ -1068,23 +908,6 @@ public virtual bool IsCustomerAllowedToSubscribe(Customer customer) return true; } - public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId) - { - int pageIndex = 0; - var forumPosts = GetAllPosts(forumTopicId, 0, string.Empty, true, 0, int.MaxValue); - - for (int i = 0; i < forumPosts.TotalCount; i++) - { - if (forumPosts[i].Id == postId) - { - if (pageSize > 0) - { - pageIndex = i / pageSize; - } - } - } - - return pageIndex; - } + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index 46e0f2f428..cf743e3411 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -10,11 +10,7 @@ namespace SmartStore.Services.Forums /// public partial interface IForumService { - /// - /// Deletes a forum group - /// - /// Forum group - void DeleteForumGroup(ForumGroup forumGroup); + #region Group /// /// Gets a forum group @@ -26,8 +22,15 @@ public partial interface IForumService /// /// Gets all forum groups /// + /// Store identifier /// Forum groups - IList GetAllForumGroups(bool showHidden = false); + IList GetAllForumGroups(int storeId = 0); + + /// + /// Deletes a forum group + /// + /// Forum group + void DeleteForumGroup(ForumGroup forumGroup); /// /// Inserts a forum group @@ -41,11 +44,9 @@ public partial interface IForumService /// Forum group void UpdateForumGroup(ForumGroup forumGroup); - /// - /// Deletes a forum - /// - /// Forum - void DeleteForum(Forum forum); + #endregion + + #region Forum /// /// Gets a forum @@ -61,6 +62,12 @@ public partial interface IForumService /// Forums IList GetAllForumsByGroupId(int forumGroupId); + /// + /// Deletes a forum + /// + /// Forum + void DeleteForum(Forum forum); + /// /// Inserts a forum /// @@ -73,11 +80,9 @@ public partial interface IForumService /// Forum void UpdateForum(Forum forum); - /// - /// Deletes a forum topic - /// - /// Forum topic - void DeleteTopic(ForumTopic forumTopic); + #endregion + + #region Topic /// /// Gets a forum topic @@ -87,25 +92,20 @@ public partial interface IForumService ForumTopic GetTopicById(int forumTopicId); /// - /// Gets a forum topic + /// Gets forum topics by identifiers /// - /// The forum topic identifier - /// The value indicating whether to increase forum topic views - /// Forum Topic - ForumTopic GetTopicById(int forumTopicId, bool increaseViews); + /// Array of topic identifiers + /// List of topics + IList GetTopicsByIds(int[] topicIds); /// /// Gets all forum topics /// /// The forum identifier - /// The customer identifier - /// Keywords - /// Search type - /// Limit by the last number days; 0 to load all topics /// Page index /// Page size /// Forum Topics - IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize); + IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize); /// /// Gets active forum topics @@ -115,6 +115,12 @@ public partial interface IForumService /// Forum Topics IList GetActiveTopics(int forumId, int topicCount); + /// + /// Deletes a forum topic + /// + /// Forum topic + void DeleteTopic(ForumTopic forumTopic); + /// /// Inserts a forum topic /// @@ -137,10 +143,17 @@ public partial interface IForumService ForumTopic MoveTopic(int forumTopicId, int newForumId); /// - /// Deletes a forum post + /// Calculates topic page index by post identifier /// - /// Forum post - void DeletePost(ForumPost forumPost); + /// Topic identifier + /// Page size + /// Post identifier + /// Page index + int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); + + #endregion + + #region Post /// /// Gets a forum post @@ -150,29 +163,28 @@ public partial interface IForumService ForumPost GetPostById(int forumPostId); /// - /// Gets all forum posts + /// Gets forum posts by identifiers. /// - /// The forum topic identifier - /// The customer identifier - /// Keywords - /// Page index - /// Page size - /// Posts - IPagedList GetAllPosts(int forumTopicId, - int customerId, string keywords, int pageIndex, int pageSize); + /// Forum post identfiers. + /// Forum posts. + IList GetPostsByIds(int[] postIds); /// /// Gets all forum posts /// /// The forum topic identifier /// The customer identifier - /// Keywords /// Sort order /// Page index /// Page size /// Forum Posts - IPagedList GetAllPosts(int forumTopicId, int customerId, - string keywords, bool ascSort, int pageIndex, int pageSize); + IPagedList GetAllPosts(int forumTopicId, int customerId, bool ascSort, int pageIndex, int pageSize); + + /// + /// Deletes a forum post + /// + /// Forum post + void DeletePost(ForumPost forumPost); /// /// Inserts a forum post @@ -187,11 +199,9 @@ IPagedList GetAllPosts(int forumTopicId, int customerId, /// Forum post void UpdatePost(ForumPost forumPost); - /// - /// Deletes a private message - /// - /// Private message - void DeletePrivateMessage(PrivateMessage privateMessage); + #endregion + + #region Private message /// /// Gets a private message @@ -209,13 +219,24 @@ IPagedList GetAllPosts(int forumTopicId, int customerId, /// A value indicating whether loaded messages are read. false - to load not read messages only, 1 to load read messages only, null to load all messages /// A value indicating whether loaded messages are deleted by author. false - messages are not deleted by author, null to load all messages /// A value indicating whether loaded messages are deleted by recipient. false - messages are not deleted by recipient, null to load all messages - /// Keywords /// Page index /// Page size /// Private messages - IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId, - int toCustomerId, bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient, - string keywords, int pageIndex, int pageSize); + IPagedList GetAllPrivateMessages( + int storeId, + int fromCustomerId, + int toCustomerId, + bool? isRead, + bool? isDeletedByAuthor, + bool? isDeletedByRecipient, + int pageIndex, + int pageSize); + + /// + /// Deletes a private message + /// + /// Private message + void DeletePrivateMessage(PrivateMessage privateMessage); /// /// Inserts a private message @@ -229,11 +250,9 @@ IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId /// Private message void UpdatePrivateMessage(PrivateMessage privateMessage); - /// - /// Deletes a forum subscription - /// - /// Forum subscription - void DeleteSubscription(ForumSubscription forumSubscription); + #endregion + + #region Subscription /// /// Gets a forum subscription @@ -254,6 +273,12 @@ IPagedList GetAllPrivateMessages(int storeId, int fromCustomerId IPagedList GetAllSubscriptions(int customerId, int forumId, int topicId, int pageIndex, int pageSize); + /// + /// Deletes a forum subscription + /// + /// Forum subscription + void DeleteSubscription(ForumSubscription forumSubscription); + /// /// Inserts a forum subscription /// @@ -266,6 +291,10 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// Forum subscription void UpdateSubscription(ForumSubscription forumSubscription); + #endregion + + #region Customer + /// /// Check whether customer is allowed to create new topics /// @@ -336,13 +365,6 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// True if allowed, otherwise false bool IsCustomerAllowedToSubscribe(Customer customer); - /// - /// Calculates topic page index by post identifier - /// - /// Topic identifier - /// Page size - /// Post identifier - /// Page index - int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); + #endregion } } diff --git a/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs b/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs index 93ca6bc3bb..eea83a391d 100644 --- a/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs +++ b/src/Libraries/SmartStore.Services/Helpers/DateTimeHelper.cs @@ -18,7 +18,8 @@ public partial class DateTimeHelper : IDateTimeHelper private TimeZoneInfo _cachedUserTimeZone; - public DateTimeHelper(IWorkContext workContext, + public DateTimeHelper( + IWorkContext workContext, IGenericAttributeService genericAttributeService, ISettingService settingService, DateTimeSettings dateTimeSettings) diff --git a/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs b/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs new file mode 100644 index 0000000000..662659d2dd --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Autofac; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Customers; +using SmartStore.Utilities; + +namespace SmartStore.Services.Hooks +{ + [Important] + public class UpdateCustomerFullNameHook : DbSaveHook + { + private static readonly HashSet _candidateProps = new HashSet(new string[] + { + TypeHelper.NameOf(x => x.Title), + TypeHelper.NameOf(x => x.Salutation), + TypeHelper.NameOf(x => x.FirstName), + TypeHelper.NameOf(x => x.LastName) + }); + + private readonly IComponentContext _ctx; + + public UpdateCustomerFullNameHook(IComponentContext ctx) + { + _ctx = ctx; + } + + protected override void OnUpdating(Customer entity, IHookedEntity entry) + { + UpdateFullName(entity, entry); + } + + protected override void OnInserting(Customer entity, IHookedEntity entry) + { + UpdateFullName(entity, entry); + } + + private void UpdateFullName(Customer entity, IHookedEntity entry) + { + var shouldUpdate = entity.IsTransientRecord(); + + if (!entity.IsTransientRecord()) + { + var modProps = _ctx.Resolve().GetModifiedProperties(entity); + shouldUpdate = _candidateProps.Any(x => modProps.ContainsKey(x)); + } + + if (shouldUpdate) + { + var parts = new[] + { + entity.Salutation, + entity.Title, + entity.FirstName, + entity.LastName + }; + + entity.FullName = string.Join(" ", parts.Where(x => x.HasValue())).NullEmpty(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs index b6c97aeb47..6c590a691a 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs @@ -24,17 +24,17 @@ public static class LocalizationExtensions /// Key selector /// When true, additionally checks whether the localized value contains empty HTML only and falls back to the default value if so. /// Localized property - public static string GetLocalized(this T entity, Expression> keySelector, bool detectEmptyHtml = false) + public static LocalizedValue GetLocalized(this T entity, Expression> keySelector, bool detectEmptyHtml = false) where T : BaseEntity, ILocalizedEntity { Guard.NotNull(entity, nameof(entity)); - return GetLocalized( + return GetLocalizedEx( entity, typeof(T).Name, entity.Id, keySelector, - EngineContext.Current.Resolve().WorkingLanguage.Id, + EngineContext.Current.Resolve().WorkingLanguage, detectEmptyHtml: detectEmptyHtml); } @@ -49,7 +49,7 @@ public static string GetLocalized(this T entity, Expression> /// A value indicating whether to ensure that we have at least two published languages; otherwise, load only default value /// When true, additionally checks whether the localized value contains empty HTML only and falls back to the default value if so. /// Localized property - public static string GetLocalized(this T entity, + public static LocalizedValue GetLocalized(this T entity, Expression> keySelector, int languageId, bool returnDefaultValue = true, @@ -59,17 +59,49 @@ public static string GetLocalized(this T entity, { Guard.NotNull(entity, nameof(entity)); - return GetLocalized( + return GetLocalizedEx( entity, typeof(T).Name, entity.Id, keySelector, - languageId, + EngineContext.Current.Resolve().GetLanguageById(languageId), returnDefaultValue, ensureTwoPublishedLanguages, detectEmptyHtml); } + /// + /// Get localized property of an entity + /// + /// Entity type + /// Entity + /// Key selector + /// Language + /// A value indicating whether to return default value (if localized is not found) + /// A value indicating whether to ensure that we have at least two published languages; otherwise, load only default value + /// When true, additionally checks whether the localized value contains empty HTML only and falls back to the default value if so. + /// Localized property + public static LocalizedValue GetLocalized(this T entity, + Expression> keySelector, + Language language, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true, + bool detectEmptyHtml = false) + where T : BaseEntity, ILocalizedEntity + { + Guard.NotNull(entity, nameof(entity)); + + return GetLocalizedEx( + entity, + typeof(T).Name, + entity.Id, + keySelector, + language, + returnDefaultValue, + ensureTwoPublishedLanguages, + detectEmptyHtml); + } + /// /// Get localized property of an entity /// @@ -82,7 +114,7 @@ public static string GetLocalized(this T entity, /// A value indicating whether to ensure that we have at least two published languages; otherwise, load only default value /// When true, additionally checks whether the localized value contains empty HTML only and falls back to the default value if so. /// Localized property - public static TPropType GetLocalized(this T entity, + public static LocalizedValue GetLocalized(this T entity, Expression> keySelector, int languageId, bool returnDefaultValue = true, @@ -92,33 +124,66 @@ public static TPropType GetLocalized(this T entity, { Guard.NotNull(entity, nameof(entity)); - return GetLocalized( + return GetLocalizedEx( entity, typeof(T).Name, entity.Id, - keySelector, - languageId, + keySelector, + EngineContext.Current.Resolve().GetLanguageById(languageId), returnDefaultValue, ensureTwoPublishedLanguages, detectEmptyHtml); } + /// + /// Get localized property of an entity + /// + /// Entity type + /// Property type + /// Entity + /// Key selector + /// Language + /// A value indicating whether to return default value (if localized is not found) + /// A value indicating whether to ensure that we have at least two published languages; otherwise, load only default value + /// When true, additionally checks whether the localized value contains empty HTML only and falls back to the default value if so. + /// Localized property + public static LocalizedValue GetLocalized(this T entity, + Expression> keySelector, + Language language, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true, + bool detectEmptyHtml = false) + where T : BaseEntity, ILocalizedEntity + { + Guard.NotNull(entity, nameof(entity)); + + return GetLocalizedEx( + entity, + typeof(T).Name, + entity.Id, + keySelector, + language, + returnDefaultValue, + ensureTwoPublishedLanguages, + detectEmptyHtml); + } + /// /// Get localized property of an instance /// /// Node /// Key selector /// Localized property - public static string GetLocalized(this ICategoryNode node, Expression> keySelector) + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector) { Guard.NotNull(node, nameof(node)); - return GetLocalized( + return GetLocalizedEx( node, "Category", node.Id, keySelector, - EngineContext.Current.Resolve().WorkingLanguage.Id); + EngineContext.Current.Resolve().WorkingLanguage); } /// @@ -128,23 +193,42 @@ public static string GetLocalized(this ICategoryNode node, ExpressionKey selector /// /// Language identifier /// Localized property - public static string GetLocalized(this ICategoryNode node, Expression> keySelector, int languageId) + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector, int languageId) + { + Guard.NotNull(node, nameof(node)); + + return GetLocalizedEx( + node, + "Category", + node.Id, + keySelector, + EngineContext.Current.Resolve().GetLanguageById(languageId)); + } + + /// + /// Get localized property of an instance + /// + /// Node + /// Key selector + /// /// Language + /// Localized property + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector, Language language) { Guard.NotNull(node, nameof(node)); - return GetLocalized( + return GetLocalizedEx( node, "Category", node.Id, keySelector, - languageId); + language); } - internal static TPropType GetLocalized(T entity, + internal static LocalizedValue GetLocalizedEx(T entity, string localeKeyGroup, int entityId, Expression> keySelector, - int languageId, + Language requestLanguage, bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true, bool detectEmptyHtml = false) @@ -153,67 +237,143 @@ internal static TPropType GetLocalized(T entity, TPropType result = default(TPropType); string resultStr = string.Empty; - if (entityId > 0) + var member = keySelector.Body as MemberExpression; + if (member == null) { - var member = keySelector.Body as MemberExpression; - if (member == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); - } + throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); + } - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) + { + throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); + } + + var languageService = EngineContext.Current.Resolve(); + + Language currentLanguage = null; - // Load localized value - string localeKey = propInfo.Name; + // Load localized value + string localeKey = propInfo.Name; + + if (requestLanguage != null) + { + // Ensure that we have at least two published languages + bool loadLocalizedValue = true; + if (ensureTwoPublishedLanguages) + { + var totalPublishedLanguages = languageService.GetLanguagesCount(false); + loadLocalizedValue = totalPublishedLanguages >= 2; + } - if (languageId > 0) + // Localized value + if (loadLocalizedValue) { - // Ensure that we have at least two published languages - bool loadLocalizedValue = true; - if (ensureTwoPublishedLanguages) + var leService = EngineContext.Current.Resolve(); + resultStr = leService.GetLocalizedValue(requestLanguage.Id, entityId, localeKeyGroup, localeKey); + + if (detectEmptyHtml && resultStr.HasValue() && resultStr.RemoveHtml().IsEmpty()) { - var lService = EngineContext.Current.Resolve(); - var totalPublishedLanguages = lService.GetLanguagesCount(false); - loadLocalizedValue = totalPublishedLanguages >= 2; + resultStr = string.Empty; } - // Localized value - if (loadLocalizedValue) + if (resultStr.HasValue()) { - var leService = EngineContext.Current.Resolve(); - resultStr = leService.GetLocalizedValue(languageId, entityId, localeKeyGroup, localeKey); - - if (detectEmptyHtml && resultStr.HasValue() && resultStr.RemoveHtml().IsEmpty()) - { - resultStr = string.Empty; - } - - if (resultStr.HasValue()) - { - result = (TPropType)resultStr.Convert(typeof(TPropType), CultureInfo.InvariantCulture); - } + currentLanguage = requestLanguage; + result = (TPropType)resultStr.Convert(typeof(TPropType), CultureInfo.InvariantCulture); } } } - + // Set default value if required if (returnDefaultValue && resultStr.IsEmpty()) { + currentLanguage = languageService.GetLanguageById(languageService.GetDefaultLanguageId()); var localizer = keySelector.Compile(); result = localizer(entity); } - return result; + if (requestLanguage == null) + { + requestLanguage = EngineContext.Current.Resolve().WorkingLanguage; + } + + if (currentLanguage == null) + { + currentLanguage = requestLanguage; + } + + return new LocalizedValue(result, requestLanguage, currentLanguage); } + //internal static TPropType GetLocalized(T entity, + // string localeKeyGroup, + // int entityId, + // Expression> keySelector, + // int languageId, + // bool returnDefaultValue = true, + // bool ensureTwoPublishedLanguages = true, + // bool detectEmptyHtml = false) + // where T : ILocalizedEntity + //{ + // TPropType result = default(TPropType); + // string resultStr = string.Empty; + + // var member = keySelector.Body as MemberExpression; + // if (member == null) + // { + // throw new ArgumentException($"Expression '{keySelector}' refers to a method, not a property."); + // } + + // var propInfo = member.Member as PropertyInfo; + // if (propInfo == null) + // { + // throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); + // } + + // // Load localized value + // string localeKey = propInfo.Name; + + // if (languageId > 0) + // { + // // Ensure that we have at least two published languages + // bool loadLocalizedValue = true; + // if (ensureTwoPublishedLanguages) + // { + // var lService = EngineContext.Current.Resolve(); + // var totalPublishedLanguages = lService.GetLanguagesCount(false); + // loadLocalizedValue = totalPublishedLanguages >= 2; + // } + + // // Localized value + // if (loadLocalizedValue) + // { + // var leService = EngineContext.Current.Resolve(); + // resultStr = leService.GetLocalizedValue(languageId, entityId, localeKeyGroup, localeKey); + + // if (detectEmptyHtml && resultStr.HasValue() && resultStr.RemoveHtml().IsEmpty()) + // { + // resultStr = string.Empty; + // } + + // if (resultStr.HasValue()) + // { + // result = (TPropType)resultStr.Convert(typeof(TPropType), CultureInfo.InvariantCulture); + // } + // } + // } + + // // Set default value if required + // if (returnDefaultValue && resultStr.IsEmpty()) + // { + // var localizer = keySelector.Compile(); + // result = localizer(entity); + // } + + // return result; + //} + + /// /// Get localized value of enum diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index bac41c3830..60a56d3b8e 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -61,7 +61,7 @@ public virtual void DeleteLocaleStringResource(LocaleStringResource resource) Guard.NotNull(resource, nameof(resource)); // cache - ClearCachedResourceSegment(resource.ResourceName, resource.LanguageId); + ClearCacheSegment(resource.ResourceName, resource.LanguageId); // db _lsrRepository.Delete(resource); @@ -77,7 +77,7 @@ public virtual int DeleteLocaleStringResources(string key, bool keyIsRootKey = t var sqlDelete = "Delete From LocaleStringResource Where ResourceName Like '{0}%'".FormatWith(key.EndsWith(".") || !keyIsRootKey ? key : key + "."); result = _dbContext.ExecuteSqlCommand(sqlDelete); - ClearCachedResourceSegment(key); + ClearCacheSegment(key); } catch (Exception exc) { @@ -148,7 +148,7 @@ public virtual void InsertLocaleStringResource(LocaleStringResource resource) _lsrRepository.Insert(resource); // cache - ClearCachedResourceSegment(resource.ResourceName, resource.LanguageId); + ClearCacheSegment(resource.ResourceName, resource.LanguageId); } public virtual void UpdateLocaleStringResource(LocaleStringResource resource) @@ -159,18 +159,18 @@ public virtual void UpdateLocaleStringResource(LocaleStringResource resource) object origKey = null; if (_dbContext.TryGetModifiedProperty(resource, "ResourceName", out origKey)) { - ClearCachedResourceSegment((string)origKey, resource.LanguageId); + ClearCacheSegment((string)origKey, resource.LanguageId); } - ClearCachedResourceSegment(resource.ResourceName, resource.LanguageId); + ClearCacheSegment(resource.ResourceName, resource.LanguageId); _lsrRepository.Update(resource); } - protected virtual IDictionary GetCachedResourceSegment(string forKey, int languageId) + protected virtual IDictionary GetCacheSegment(string forKey, int languageId) { Guard.NotEmpty(forKey, nameof(forKey)); - var segmentKey = GetSegmentKey(forKey); + var segmentKey = GetSegmentKeyPart(forKey); var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); return _cacheManager.Get(cacheKey, () => @@ -196,9 +196,9 @@ protected virtual IDictionary GetCachedResourceSegment(string fo /// /// The resource key for which a segment key should be created /// Language Id. If null, segments for all cached languages will be invalidated - protected virtual void ClearCachedResourceSegment(string forKey, int? languageId = null) + protected virtual void ClearCacheSegment(string forKey, int? languageId = null) { - var segmentKey = GetSegmentKey(forKey); + var segmentKey = GetSegmentKeyPart(forKey); if (languageId.HasValue && languageId.Value > 0) { @@ -231,7 +231,7 @@ public virtual string GetResource( resourceKey = resourceKey.EmptyNull().Trim().ToLowerInvariant(); - var cachedSegment = GetCachedResourceSegment(resourceKey, languageId); + var cachedSegment = GetCacheSegment(resourceKey, languageId); if (!cachedSegment.TryGetValue(resourceKey, out result)) { @@ -561,8 +561,8 @@ public virtual int ImportResourcesFromXml( { var segmentKeys = new HashSet(); - toAdd.Each(x => segmentKeys.Add(GetSegmentKey(x.ResourceName))); - toUpdate.Each(x => segmentKeys.Add(GetSegmentKey(x.ResourceName))); + toAdd.Each(x => segmentKeys.Add(GetSegmentKeyPart(x.ResourceName))); + toUpdate.Each(x => segmentKeys.Add(GetSegmentKeyPart(x.ResourceName))); _lsrRepository.InsertRange(toAdd); toAdd.Clear(); @@ -575,7 +575,7 @@ public virtual int ImportResourcesFromXml( // clear cache foreach (var segmentKey in segmentKeys) { - ClearCachedResourceSegment(segmentKey, language.Id); + ClearCacheSegment(segmentKey, language.Id); } return num; @@ -690,7 +690,7 @@ private string BuildCacheSegmentKey(string segment, int languageId) return String.Format(LOCALESTRINGRESOURCES_SEGMENT_KEY, segment, languageId); } - private string GetSegmentKey(string forKey) + private string GetSegmentKeyPart(string forKey) { return forKey.Substring(0, Math.Min(forKey.Length, 3)).ToLowerInvariant(); } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs index ca58d76a85..8e618e792c 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -19,7 +19,7 @@ public partial class LocalizedEntityService : ScopedServiceBase, ILocalizedEntit /// 0 = segment (keygroup.key.idrange), 1 = language id /// const string LOCALIZEDPROPERTY_SEGMENT_KEY = "localizedproperty:{0}-lang-{1}"; - const string LOCALIZEDPROPERTY_SEGMENT_PATTERN = "localizedproperty:{0}"; + const string LOCALIZEDPROPERTY_SEGMENT_PATTERN = "localizedproperty:{0}*"; const string LOCALIZEDPROPERTY_ALLSEGMENTS_PATTERN = "localizedproperty:*"; private readonly IRepository _localizedPropertyRepository; @@ -38,12 +38,12 @@ protected override void OnClearCache() _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_ALLSEGMENTS_PATTERN); } - protected virtual IDictionary GetCachedPropertySegment(string localeKeyGroup, string localeKey, int entityId, int languageId) + protected virtual IDictionary GetCacheSegment(string localeKeyGroup, string localeKey, int entityId, int languageId) { Guard.NotEmpty(localeKeyGroup, nameof(localeKeyGroup)); Guard.NotEmpty(localeKey, nameof(localeKey)); - var segmentKey = GetSegmentKey(localeKeyGroup, localeKey, entityId, out var minEntityId, out var maxEntityId); + var segmentKey = GetSegmentKeyPart(localeKeyGroup, localeKey, entityId, out var minEntityId, out var maxEntityId); var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); // TODO: (MC) skip caching product.fulldescription (?), OR @@ -69,21 +69,25 @@ protected virtual IDictionary GetCachedPropertySegment(string local /// /// Clears the cached segment from the cache /// - protected virtual void ClearCachedPropertySegment(string localeKeyGroup, string localeKey, int entityId, int? languageId = null) + protected virtual void ClearCacheSegment(string localeKeyGroup, string localeKey, int entityId, int? languageId = null) { - if (IsInScope) - return; + try + { + if (IsInScope) + return; - var segmentKey = GetSegmentKey(localeKeyGroup, localeKey, entityId); + var segmentKey = GetSegmentKeyPart(localeKeyGroup, localeKey, entityId); - if (languageId.HasValue && languageId.Value > 0) - { - _cacheManager.Remove(BuildCacheSegmentKey(segmentKey, languageId.Value)); - } - else - { - _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_SEGMENT_PATTERN.FormatInvariant(segmentKey)); + if (languageId.HasValue && languageId.Value > 0) + { + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey, languageId.Value)); + } + else + { + _cacheManager.RemoveByPattern(LOCALIZEDPROPERTY_SEGMENT_PATTERN.FormatInvariant(segmentKey)); + } } + catch { } } public virtual string GetLocalizedValue(int languageId, int entityId, string localeKeyGroup, string localeKey) @@ -96,7 +100,7 @@ public virtual string GetLocalizedValue(int languageId, int entityId, string loc if (languageId <= 0) return string.Empty; - var props = GetCachedPropertySegment(localeKeyGroup, localeKey, entityId, languageId); + var props = GetCacheSegment(localeKeyGroup, localeKey, entityId, languageId); if (!props.TryGetValue(entityId, out var val)) { @@ -158,12 +162,8 @@ public virtual void InsertLocalizedProperty(LocalizedProperty property) _localizedPropertyRepository.Insert(property); HasChanges = true; - try - { - // cache - ClearCachedPropertySegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); - } - catch { } + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); } public virtual void UpdateLocalizedProperty(LocalizedProperty property) @@ -174,24 +174,16 @@ public virtual void UpdateLocalizedProperty(LocalizedProperty property) _localizedPropertyRepository.Update(property); HasChanges = true; - try - { - // cache - ClearCachedPropertySegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); - } - catch { } + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); } public virtual void DeleteLocalizedProperty(LocalizedProperty property) { Guard.NotNull(property, nameof(property)); - try - { - // cache - ClearCachedPropertySegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); - } - catch { } + // cache + ClearCacheSegment(property.LocaleKeyGroup, property.LocaleKey, property.EntityId, property.LanguageId); // db _localizedPropertyRepository.Delete(property); @@ -282,23 +274,15 @@ private string BuildCacheSegmentKey(string segment, int languageId) return String.Format(LOCALIZEDPROPERTY_SEGMENT_KEY, segment, languageId); } - private string GetSegmentKey(string localeKeyGroup, string localeKey, int entityId) + private string GetSegmentKeyPart(string localeKeyGroup, string localeKey, int entityId) { - return GetSegmentKey(localeKeyGroup, localeKey, entityId, out var minId, out var maxId); + return GetSegmentKeyPart(localeKeyGroup, localeKey, entityId, out var minId, out var maxId); } - private string GetSegmentKey(string localeKeyGroup, string localeKey, int entityId, out int minId, out int maxId) + private string GetSegmentKeyPart(string localeKeyGroup, string localeKey, int entityId, out int minId, out int maxId) { - minId = 0; - maxId = 0; - - // max 500 values per cache item - var entityRange = Math.Ceiling((decimal)entityId / 500) * 500; - - maxId = (int)entityRange; - minId = maxId - 499; - - return (localeKeyGroup + "." + localeKey + "." + entityRange.ToString()).ToLowerInvariant(); + maxId = entityId.GetRange(500, out minId); + return (localeKeyGroup + "." + localeKey + "." + maxId.ToString()).ToLowerInvariant(); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs index 33861ceb44..6f908c3ccc 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedUrlHelper.cs @@ -67,8 +67,7 @@ public bool IsLocalizedUrl(out string seoCode) public string StripSeoCode() { - string seoCode; - if (IsLocalizedUrl(out seoCode)) + if (IsLocalizedUrl(out var seoCode)) { this.RelativePath = this.RelativePath.Substring(seoCode.Length).TrimStart('/'); //if (this.RelativePath.IsEmpty()) @@ -100,7 +99,7 @@ public string PrependSeoCode(string seoCode, bool safe = false) } } - this.RelativePath = "{0}/{1}".FormatCurrent(seoCode, this.RelativePath); + this.RelativePath = "{0}/{1}".FormatCurrent(seoCode, this.RelativePath).TrimEnd('/'); return this.RelativePath; } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedValue.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedValue.cs new file mode 100644 index 0000000000..ed3c57dcf4 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedValue.cs @@ -0,0 +1,156 @@ +using System; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using Newtonsoft.Json; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore.Services.Localization +{ + public class LocalizedValue + { + // Regex for all types of brackets which need to be "swapped": ({[]}) + private readonly static Regex _rgBrackets = new Regex(@"\(|\{|\[|\]|\}|\)", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + /// + /// Fixes the flow of brackets within a text if the current page language has RTL flow. + /// + /// The test to fix. + /// Current language + /// + public static string FixBrackets(string str, Language currentLanguage) + { + if (!currentLanguage.Rtl || str.IsEmpty()) + { + return str; + } + + var controlChar = "‎"; + return _rgBrackets.Replace(str, m => + { + return controlChar + m.Value + controlChar; + }); + } + } + + [Serializable] + public class LocalizedValue : IHtmlString, IEquatable>, IComparable, IComparable> + { + private readonly T _value; + private readonly Language _requestLanguage; + private readonly Language _currentLanguage; + + public LocalizedValue(T value, Language requestLanguage, Language currentLanguage) + { + _value = value; + _requestLanguage = requestLanguage; + _currentLanguage = currentLanguage; + } + + public T Value + { + get { return _value; } + } + + [JsonIgnore] + public Language RequestLanguage + { + get { return _requestLanguage; } + } + + [JsonIgnore] + public Language CurrentLanguage + { + get { return _currentLanguage; } + } + + public bool IsFallback + { + get { return _requestLanguage != _currentLanguage; } + } + + public bool BidiOverride + { + get { return _requestLanguage != _currentLanguage && _requestLanguage.Rtl != _currentLanguage.Rtl; } + } + + public static implicit operator T(LocalizedValue obj) + { + if (obj == null) + { + return default; + } + + return obj.Value; + } + + public string ToHtmlString() + { + return this.ToString(); + } + + public override string ToString() + { + if (_value == null) + { + return null; + } + + if (typeof(T) == typeof(string)) + { + return _value as string; + } + + return _value.Convert(CultureInfo.GetCultureInfo(_currentLanguage.LanguageCulture)); + } + + public override int GetHashCode() + { + var hashCode = 0; + if (_value != null) + hashCode ^= _value.GetHashCode(); + return hashCode; + } + + public override bool Equals(object other) + { + return this.Equals(other as LocalizedValue); + } + + public bool Equals(LocalizedValue other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return object.Equals(_value, other._value); + } + + public int CompareTo(object other) + { + return CompareTo(other as LocalizedValue); + } + + public int CompareTo(LocalizedValue other) + { + if (other == null) + { + return 1; + } + + if (Value is IComparable val1) + { + return val1.CompareTo(other.Value); + } + + if (Value is IComparable val2) + { + return val2.CompareTo(other.Value); + } + + return 0; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs b/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs index 61ba8f21ad..6c45b687c9 100644 --- a/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs +++ b/src/Libraries/SmartStore.Services/Logging/CustomerActivityService.cs @@ -232,7 +232,7 @@ public virtual IPagedList GetAllActivities( var queryCustomers = _customerRepository.Table; if (email.HasValue()) - queryCustomers = queryCustomers.Where(x => x.Email == email); + queryCustomers = queryCustomers.Where(x => x.Email.Contains(email)); if (customerSystemAccount.HasValue) queryCustomers = queryCustomers.Where(x => x.IsSystemAccount == customerSystemAccount.Value); diff --git a/src/Libraries/SmartStore.Services/Logging/DbLogService.cs b/src/Libraries/SmartStore.Services/Logging/DbLogService.cs index 53f10a7937..d725fadef6 100644 --- a/src/Libraries/SmartStore.Services/Logging/DbLogService.cs +++ b/src/Libraries/SmartStore.Services/Logging/DbLogService.cs @@ -88,8 +88,8 @@ public virtual IPagedList GetAllLogs( string logger, string message, LogLevel? logLevel, - int pageIndex, int - pageSize) + int pageIndex, + int pageSize) { // force flush to get most recent entries _loggerFactory.FlushAll(); diff --git a/src/Libraries/SmartStore.Services/Media/DownloadService.cs b/src/Libraries/SmartStore.Services/Media/DownloadService.cs index 1f36eb41af..16a0feee63 100644 --- a/src/Libraries/SmartStore.Services/Media/DownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/DownloadService.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using NuGet; +using SmartStore.Collections; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; @@ -13,7 +16,7 @@ namespace SmartStore.Services.Media { - public partial class DownloadService : IDownloadService + public partial class DownloadService : IDownloadService { private readonly IRepository _downloadRepository; private readonly IEventPublisher _eventPubisher; @@ -69,6 +72,67 @@ where downloadIds.Contains(dl.Id) // sort by passed identifier sequence return downloads.OrderBySequence(downloadIds).ToList(); } + + public virtual IList GetDownloadsFor(TEntity entity) where TEntity : BaseEntity + { + Guard.NotNull(entity, nameof(entity)); + + return GetDownloadsFor(entity.Id, entity.GetUnproxiedType().Name); + } + + public virtual IList GetDownloadsFor(int entityId, string entityName) + { + if (entityId > 0) + { + var downloads = (from x in _downloadRepository.Table + where x.EntityId == entityId && x.EntityName == entityName + select x).ToList(); + + if (downloads.Any()) + { + var idsOrderedByVersion = downloads + .Select(x => new { x.Id, Version = new SemanticVersion(x.FileVersion.HasValue() ? x.FileVersion : "1.0.0.0") }) + .OrderBy(x => x.Version) + .Select(x => x.Id); + + downloads = new List(downloads.OrderBySequence(idsOrderedByVersion)); + + return downloads; + } + } + + return new List(); + } + + public virtual Download GetDownloadByVersion(int entityId, string entityName, string fileVersion) + { + if (entityId > 0 && fileVersion.HasValue() && entityName.HasValue()) + { + var download = (from x in _downloadRepository.Table + where x.EntityId == entityId && x.EntityName.Equals(entityName) && x.FileVersion.Equals(fileVersion) + select x).FirstOrDefault(); + + return download; + } + + return null; + } + + public virtual Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName) + { + Guard.NotNull(entityIds, nameof(entityIds)); + Guard.NotEmpty(entityName, nameof(entityName)); + + var query = _downloadRepository.TableUntracked + .Where(x => entityIds.Contains(x.EntityId) && x.EntityName == entityName) + .OrderBy(x => x.FileVersion); + + var map = query + .ToList() + .ToMultimap(x => x.EntityId, x => x); + + return map; + } public virtual Download GetDownloadByGuid(Guid downloadGuid) { diff --git a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs index 389b002e8d..245bfe9bb3 100644 --- a/src/Libraries/SmartStore.Services/Media/IDownloadService.cs +++ b/src/Libraries/SmartStore.Services/Media/IDownloadService.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using SmartStore.Collections; +using SmartStore.Core; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Media { - public partial interface IDownloadService + public partial interface IDownloadService { /// /// Gets a download @@ -21,6 +23,38 @@ public partial interface IDownloadService /// List of download entities IList GetDownloadsByIds(int[] downloadIds); + /// + /// Gets downloads assigned to an entity + /// + /// Entity to get download for + /// List of download entities sorted by FileVersion + IList GetDownloadsFor(TEntity entity) where TEntity : BaseEntity; + + /// + /// Gets downloads by entity identifier + /// + /// Entity identifier + /// Entity name + /// List of download entities + IList GetDownloadsFor(int entityId, string entityName); + + /// + /// Gets downloads by entity identifier & fileversion + /// + /// Entity identifier + /// Entity name + /// File version + /// Download entity + Download GetDownloadByVersion(int entityId, string entityName, string fileVersion); + + /// + /// Gets downloads by entity identifiers + /// + /// Entity identifiers + /// Entity name + /// Multimap of download entities + Multimap GetDownloadsByEntityIds(int[] entityIds, string entityName); + /// /// Gets a download by GUID /// diff --git a/src/Libraries/SmartStore.Services/Media/IImageCache.cs b/src/Libraries/SmartStore.Services/Media/IImageCache.cs index 9f11f17886..97b08e2cf7 100644 --- a/src/Libraries/SmartStore.Services/Media/IImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/IImageCache.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using SmartStore.Core.Domain.Media; +using SmartStore.Core.IO; namespace SmartStore.Services.Media { @@ -40,6 +41,16 @@ public interface IImageCache /// If the requested image does not exist in the cache, the value of the Exists property will be false. CachedImageResult Get(int? pictureId, string seoFileName, string extension, ProcessImageQuery query = null); + /// + /// Gets an instance of the object, which contains information about a cached image. + /// Use this overload to get thumbnail info about uploaded media manager asset files. + /// + /// The file to get info about. + /// The image processing query. + /// An instance of the object + /// If the requested image does not exist in the cache, the value of the Exists property will be false. + CachedImageResult Get(IFile file, ProcessImageQuery query); + /// /// Opens a readonly file stream to the cached image /// @@ -53,10 +64,16 @@ public interface IImageCache /// The for which to delete cached images void Delete(Picture picture); - /// - /// Deletes all cached images (nukes all files in the cache folder) - /// - void Clear(); + /// + /// Deletes all cached images for the given + /// + /// The for which to delete cached images + void Delete(IFile file); + + /// + /// Deletes all cached images (nukes all files in the cache folder) + /// + void Clear(); /// /// Refreshes the file info. diff --git a/src/Libraries/SmartStore.Services/Media/IPictureService.cs b/src/Libraries/SmartStore.Services/Media/IPictureService.cs index cc982c3f48..84c6e2013c 100644 --- a/src/Libraries/SmartStore.Services/Media/IPictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/IPictureService.cs @@ -7,6 +7,7 @@ using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; +using SmartStore.Core.IO; namespace SmartStore.Services.Media { @@ -78,8 +79,9 @@ public partial interface IPictureService /// Gets the size of a picture /// /// The buffer + /// Passing MIME type can slightly speed things up /// Size - Size GetPictureSize(byte[] pictureBinary); + Size GetPictureSize(byte[] pictureBinary, string mimeType = null); /// /// TODO: (mc) @@ -267,8 +269,7 @@ public static Picture UpdatePicture(this IPictureService pictureService, public static Size GetPictureSize(this IPictureService pictureService, Picture picture) { - var pictureBinary = pictureService.LoadPictureBinary(picture); - return pictureService.GetPictureSize(pictureBinary); + return ImageHeader.GetDimensions(pictureService.OpenPictureStream(picture), picture.MimeType, false); } /// diff --git a/src/Libraries/SmartStore.Services/Media/ImageCache.cs b/src/Libraries/SmartStore.Services/Media/ImageCache.cs index 3065dc27b2..b6f005e920 100644 --- a/src/Libraries/SmartStore.Services/Media/ImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/ImageCache.cs @@ -121,10 +121,38 @@ public virtual CachedImageResult Get(int? pictureId, string seoFileName, string Extension = extension, IsRemote = _fileSystem.IsCloudStorage }; - - return result; + + if (file.Exists && file.Size <= 0) + { + result.Exists = false; + } + + return result; } + public virtual CachedImageResult Get(IFile file, ProcessImageQuery query) + { + Guard.NotNull(file, nameof(file)); + Guard.NotNull(query, nameof(query)); + + var imagePath = GetCachedImagePath(file, query); + var thumbFile = _fileSystem.GetFile(BuildPath(imagePath)); + + var result = new CachedImageResult(thumbFile) + { + Path = imagePath, + Extension = file.Extension.TrimStart('.'), + IsRemote = _fileSystem.IsCloudStorage + }; + + if (file.Exists && file.Size <= 0) + { + result.Exists = false; + } + + return result; + } + public virtual Stream Open(CachedImageResult cachedImage) { Guard.NotNull(cachedImage, nameof(cachedImage)); @@ -137,7 +165,7 @@ public virtual string GetPublicUrl(string imagePath) if (imagePath.IsEmpty()) return null; - return _fileSystem.GetPublicUrl(BuildPath(imagePath)).EmptyNull(); + return _fileSystem.GetPublicUrl(BuildPath(imagePath), true).EmptyNull(); } public virtual void RefreshInfo(CachedImageResult cachedImage) @@ -146,7 +174,7 @@ public virtual void RefreshInfo(CachedImageResult cachedImage) var file = _fileSystem.GetFile(cachedImage.File.Path); cachedImage.File = file; - cachedImage.Exists = file.Exists; + cachedImage.Exists = file.Exists && file.Size > 0; } public virtual void Delete(Picture picture) @@ -160,7 +188,19 @@ public virtual void Delete(Picture picture) } } - public virtual void Clear() + public virtual void Delete(IFile file) + { + // TODO: (mc) this could lead to more thumbs getting deleted as desired. But who cares? :-) + var filter = string.Format("{0}*.*", file.Title); + + var files = _fileSystem.SearchFiles(BuildPath(file.Directory), filter); + foreach (var f in files) + { + _fileSystem.DeleteFile(f); + } + } + + public virtual void Clear() { for (int i = 0; i < 10; i++) { @@ -248,6 +288,28 @@ private string GetCachedImagePath(int? pictureId, string seoFileName, string ext return String.Concat(imageFileName, ".", extension); } + /// + /// Returns the images thumb path as is plus query (required for uploaded images) + /// + /// Image file to get thumbnail for + /// + /// + private string GetCachedImagePath(IFile file, ProcessImageQuery query) + { + if (!_imageProcessor.IsSupportedImage(file.Name)) + { + throw new InvalidOperationException("Thumbnails for '{0}' files are not supported".FormatInvariant(file.Extension)); + } + + // TODO: (mc) prevent creating thumbs for thumbs AND check equality of source and target + + var imageFileName = String.Concat(file.Title, query.CreateHash()); + var extension = (query.GetResultExtension() ?? file.Extension).EnsureStartsWith(".").ToLower(); + var path = _fileSystem.Combine(file.Directory, imageFileName + extension); + + return path.TrimStart('/', '\\'); + } + private string BuildPath(string imagePath) { if (imagePath.IsEmpty()) diff --git a/src/Libraries/SmartStore.Services/Media/ImageHeader.cs b/src/Libraries/SmartStore.Services/Media/ImageHeader.cs deleted file mode 100644 index 1385f6d55a..0000000000 --- a/src/Libraries/SmartStore.Services/Media/ImageHeader.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; - -namespace SmartStore.Services.Media -{ - /// - /// Taken from http://stackoverflow.com/questions/111345/getting-image-dimensions-without-reading-the-entire-file/111349 - /// Minor improvements including supporting unsigned 16-bit integers when decoding Jfif and added logic - /// to load the image using new Bitmap if reading the headers fails - /// - public static class ImageHeader - { - internal class UnknownImageFormatException : ArgumentException - { - public UnknownImageFormatException(string paramName = "", Exception e = null) - : base("Could not recognise image format.", paramName, e) - { - } - } - - private static Dictionary> _imageFormatDecoders = new Dictionary>() - { - { new byte[] { 0x42, 0x4D }, DecodeBitmap }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, - { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, - { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, - { new byte[] { 0xff, 0xd8 }, DecodeJfif }, - }; - - private static int _maxMagicBytesLength = 0; - - static ImageHeader() - { - _maxMagicBytesLength = _imageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; - } - - /// - /// Gets the dimensions of an image. - /// - /// The path of the image to get the dimensions of. - /// The dimensions of the specified image. - /// The image was of an unrecognised format. - public static Size GetDimensions(string path) - { - try - { - using (BinaryReader binaryReader = new BinaryReader(File.OpenRead(path))) - { - try - { - return GetDimensions(binaryReader); - } - catch (ArgumentException e) - { - throw new UnknownImageFormatException("path", e); - } - } - } - catch (ArgumentException) - { - using (var b = new Bitmap(path)) - { - return b.Size; - } - } - } - - /// - /// Gets the dimensions of an image. - /// - /// The path of the image to get the dimensions of. - /// The dimensions of the specified image. - /// The image was of an unrecognised format. - public static Size GetDimensions(BinaryReader binaryReader) - { - byte[] magicBytes = new byte[_maxMagicBytesLength]; - for (int i = 0; i < _maxMagicBytesLength; i += 1) - { - magicBytes[i] = binaryReader.ReadByte(); - foreach (var kvPair in _imageFormatDecoders) - { - if (StartsWith(magicBytes, kvPair.Key)) - { - return kvPair.Value(binaryReader); - } - } - } - - throw new UnknownImageFormatException("binaryReader"); - } - - private static bool StartsWith(byte[] thisBytes, byte[] thatBytes) - { - for (int i = 0; i < thatBytes.Length; i += 1) - { - if (thisBytes[i] != thatBytes[i]) - { - return false; - } - } - - return true; - } - - private static short ReadLittleEndianInt16(BinaryReader binaryReader) - { - byte[] bytes = new byte[sizeof(short)]; - - for (int i = 0; i < sizeof(short); i += 1) - { - bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); - } - return BitConverter.ToInt16(bytes, 0); - } - - private static ushort ReadLittleEndianUInt16(BinaryReader binaryReader) - { - byte[] bytes = new byte[sizeof(ushort)]; - - for (int i = 0; i < sizeof(ushort); i += 1) - { - bytes[sizeof(ushort) - 1 - i] = binaryReader.ReadByte(); - } - return BitConverter.ToUInt16(bytes, 0); - } - - private static int ReadLittleEndianInt32(BinaryReader binaryReader) - { - byte[] bytes = new byte[sizeof(int)]; - for (int i = 0; i < sizeof(int); i += 1) - { - bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); - } - return BitConverter.ToInt32(bytes, 0); - } - - private static Size DecodeBitmap(BinaryReader binaryReader) - { - binaryReader.ReadBytes(16); - int width = binaryReader.ReadInt32(); - int height = binaryReader.ReadInt32(); - return new Size(width, height); - } - - private static Size DecodeGif(BinaryReader binaryReader) - { - int width = binaryReader.ReadInt16(); - int height = binaryReader.ReadInt16(); - return new Size(width, height); - } - - private static Size DecodePng(BinaryReader binaryReader) - { - binaryReader.ReadBytes(8); - int width = ReadLittleEndianInt32(binaryReader); - int height = ReadLittleEndianInt32(binaryReader); - return new Size(width, height); - } - - private static Size DecodeJfif(BinaryReader binaryReader) - { - while (binaryReader.ReadByte() == 0xff) - { - byte marker = binaryReader.ReadByte(); - short chunkLength = ReadLittleEndianInt16(binaryReader); - if (marker == 0xc0) - { - binaryReader.ReadByte(); - int height = ReadLittleEndianInt16(binaryReader); - int width = ReadLittleEndianInt16(binaryReader); - return new Size(width, height); - } - - if (chunkLength < 0) - { - ushort uchunkLength = (ushort)chunkLength; - binaryReader.ReadBytes(uchunkLength - 2); - } - else - { - binaryReader.ReadBytes(chunkLength - 2); - } - } - - throw new UnknownImageFormatException(); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs b/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs index c4e0fb6a57..6317105fde 100644 --- a/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs +++ b/src/Libraries/SmartStore.Services/Media/MediaFileSystem.cs @@ -11,8 +11,10 @@ public interface IMediaFileSystem : IFileSystem public class MediaFileSystem : LocalFileSystem, IMediaFileSystem { + private static string _mediaPublicPath; + public MediaFileSystem() - : base(GetMediaBasePath(), CommonHelper.GetAppSetting("sm:MediaPublicPath")) + : base(GetMediaBasePath(), "~/" + GetMediaPublicPath()) { this.TryCreateFolder("Storage"); this.TryCreateFolder("Thumbs"); @@ -21,7 +23,7 @@ public MediaFileSystem() this.TryCreateFolder("Downloads"); } - private static string GetMediaBasePath() + public static string GetMediaBasePath() { var path = CommonHelper.GetAppSetting("sm:MediaStoragePath")?.Trim().NullEmpty(); if (path == null) @@ -31,5 +33,23 @@ private static string GetMediaBasePath() return path; } + + public static string GetMediaPublicPath() + { + if (_mediaPublicPath == null) + { + var path = CommonHelper.GetAppSetting("sm:MediaPublicPath")?.Trim().NullEmpty() ?? "media"; + + if (path.IsWebUrl()) + { + throw new NotSupportedException("Fully qualified URLs are not supported for the 'sm:MediaPublicPath' setting."); + } + + _mediaPublicPath = path.TrimStart('~', '/').Replace('\\', '/').ToLower().EnsureEndsWith("/"); + } + + + return _mediaPublicPath; + } } } diff --git a/src/Libraries/SmartStore.Services/Media/PictureService.cs b/src/Libraries/SmartStore.Services/Media/PictureService.cs index 994df865a4..4f033c9ec7 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -65,8 +65,7 @@ public partial class PictureService : IPictureService static PictureService() { - // TODO: (mc) make this configurable per web.config - _processedImagesRootPath = "media/image/"; + _processedImagesRootPath = MediaFileSystem.GetMediaPublicPath() + "image/"; _fallbackImagesRootPath = "content/images/"; } @@ -113,7 +112,7 @@ public PictureService( else if (mediaSettings.AutoGenerateAbsoluteUrls) { var uri = httpContext.Request.Url; - _host = "{0}://{1}/{2}".FormatInvariant(uri.Scheme, uri.Authority, appPath); + _host = "//{0}{1}".FormatInvariant(uri.Authority, appPath); } else { @@ -172,7 +171,7 @@ public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out size = Size.Empty; - var originalSize = GetPictureSize(pictureBinary); + var originalSize = ImageHeader.GetDimensions(pictureBinary, mimeType); var maxSize = _mediaSettings.MaximumImageSize; var query = new ProcessImageQuery(pictureBinary) @@ -261,57 +260,9 @@ public virtual Task LoadPictureBinaryAsync(Picture picture) return _storageProvider.Value.LoadAsync(picture.ToMedia()); } - public virtual Size GetPictureSize(byte[] pictureBinary) + public virtual Size GetPictureSize(byte[] pictureBinary, string mimeType = null) { - if (pictureBinary == null || pictureBinary.Length == 0) - { - return Size.Empty; - } - - return GetPictureSize(new MemoryStream(pictureBinary), false); - } - - protected virtual Size GetPictureSize(Stream input, bool leaveOpen = true) - { - Guard.NotNull(input, nameof(input)); - - var size = Size.Empty; - - if (!input.CanSeek || input.Length == 0) - { - return size; - } - - try - { - using (var reader = new BinaryReader(input, Encoding.UTF8, true)) - { - size = ImageHeader.GetDimensions(reader); - } - } - catch (Exception) - { - // something went wrong with fast image access, - // so get original size the classic way - try - { - input.Seek(0, SeekOrigin.Begin); - using (var b = new Bitmap(input)) - { - size = new Size(b.Width, b.Height); - } - } - catch { } - } - finally - { - if (!leaveOpen) - { - input.Dispose(); - } - } - - return size; + return ImageHeader.GetDimensions(pictureBinary, mimeType); } public IDictionary GetPictureInfos(IEnumerable pictureIds) @@ -522,7 +473,7 @@ private void EnsurePictureSizeResolved(Picture picture, bool saveOnResolve) { try { - var size = GetPictureSize(stream, true); + var size = ImageHeader.GetDimensions(stream, picture.MimeType, true); picture.Width = size.Width; picture.Height = size.Height; picture.UpdatedOnUtc = DateTime.UtcNow; diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs index 253a6fc033..7e316381ef 100644 --- a/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs @@ -4,7 +4,6 @@ using System.IO; using System.Collections.Specialized; using System.Text; -using System.Web.Routing; using SmartStore.Collections; using System.Drawing; using ImageProcessor.Imaging.Formats; @@ -13,6 +12,8 @@ namespace SmartStore.Services.Media { public class ProcessImageQuery : QueryString { + private readonly static HashSet _supportedTokens = new HashSet { "w", "h", "q", "m", "size" }; + public ProcessImageQuery() : this(null, new NameValueCollection()) { @@ -39,16 +40,17 @@ public ProcessImageQuery(string source) } public ProcessImageQuery(object source, NameValueCollection query) - : base(query) + : base(SanitizeCollection(query)) { Guard.NotNull(query, nameof(query)); Source = source; DisposeSource = true; + Notify = true; } public ProcessImageQuery(ProcessImageQuery query) - : base(query) + : base(SanitizeCollection(query)) { Guard.NotNull(query, nameof(query)); @@ -57,6 +59,27 @@ public ProcessImageQuery(ProcessImageQuery query) DisposeSource = query.DisposeSource; } + private static NameValueCollection SanitizeCollection(NameValueCollection query) + { + // We just need the supported flags + var sanitizable = query.AllKeys.Any(x => !_supportedTokens.Contains(x)); + if (sanitizable) + { + var copy = new NameValueCollection(query); + foreach (var key in copy.AllKeys) + { + if (!_supportedTokens.Contains(key)) + { + copy.Remove(key); + } + } + + return copy; + } + + return query; + } + /// /// The source image's physical path, app-relative virtual path, or a Stream, byte array or Image instance. /// @@ -110,6 +133,7 @@ public string ScaleMode public bool IsValidationMode { get; set; } + public bool Notify { get; set; } private T Get(string name) { @@ -125,9 +149,18 @@ private void Set(string name, T val) } - public bool NeedsProcessing() + public bool NeedsProcessing(bool ignoreQualityFlag = false) { - return base.Count > 0; + if (base.Count == 0) + return false; + + if (ignoreQualityFlag && base.Count == 1 && base["q"] != null) + { + // Return false if ignoreQualityFlag is true and "q" is the only flag. + return false; + } + + return true; } public string CreateHash() diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs index 0f8906b886..525e50d09c 100644 --- a/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs +++ b/src/Libraries/SmartStore.Services/Messages/IMessageFactory.cs @@ -76,7 +76,7 @@ public static CreateMessageResult SendContactUsMessage(this IMessageFactory fact ["Subject"] = subject.NullEmpty(), ["Message"] = message.NullEmpty(), ["SenderEmail"] = senderEmail.NullEmpty(), - ["SenderName"] = senderName.NullEmpty() + ["SenderName"] = senderName.HasValue() ? senderName.NullEmpty() : senderEmail.NullEmpty() }; var messageContext = MessageContext.Create(MessageTemplateNames.SystemContactUs, languageId, customer: customer); diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs index e584fa5863..6819624dea 100644 --- a/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/IMessageModelProvider.cs @@ -60,6 +60,28 @@ public interface IMessageModelProvider /// void AddModelPart(object part, MessageContext messageContext, string name = null); + /// + /// Creates a serializable model object for the passed entity/object. + /// + /// Supported types are: , , , , , + /// , , , , , + /// , , , , , + /// , + /// + /// + /// Furthermore, any object implementing or can also be passed as model part. + /// The first merges all entries within the passed object with the special Bag entry, the latter creates a whole + /// new entry using the name provided by its property. + /// + /// + /// If an unsupported object is passed, null is returned + /// + /// + /// The model part instance to convert. + /// Whether members/properties with null values should be excluded from the result model. + /// Optional list of member/property names to exclude from the result model. + object CreateModelPart(object part, bool ignoreNullMembers, params string[] ignoreMemberNames); + /// /// Tries to infer the model part name by type: /// diff --git a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs index d04fb30d29..14d42ddb6d 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs @@ -87,6 +87,12 @@ public virtual CreateMessageResult CreateMessage(MessageContext messageContext, // Create and assign model var model = messageContext.Model = new TemplateModel(); + // Do not create message if the template does not exist, is not authorized or not active. + if (messageContext.MessageTemplate == null) + { + return new CreateMessageResult { Model = model, MessageContext = messageContext }; + } + // Add all global template model parts _modelProvider.AddGlobalModelParts(messageContext); @@ -216,7 +222,7 @@ private string RenderTemplate(string template, MessageContext ctx, bool required private string RenderBodyTemplate(MessageContext ctx) { var key = BuildTemplateKey(ctx); - var source = ctx.MessageTemplate.GetLocalized((x) => x.Body, ctx.Language.Id); + var source = ctx.MessageTemplate.GetLocalized((x) => x.Body, ctx.Language); var fromCache = true; var template = _templateManager.GetOrAdd(key, GetBodyTemplate); @@ -357,14 +363,15 @@ private void ValidateMessageContext(MessageContext ctx, ref object[] modelParts) throw new ArgumentException("'MessageTemplateName' must not be empty if 'MessageTemplate' is null.", nameof(ctx)); } - ctx.MessageTemplate = GetActiveMessageTemplate(ctx.MessageTemplateName, ctx.Store.Id); - if (ctx.MessageTemplate == null) + ctx.MessageTemplate = _messageTemplateService.GetMessageTemplateByName(ctx.MessageTemplateName, ctx.Store.Id); + + if (ctx.MessageTemplate != null && !ctx.TestMode && !ctx.MessageTemplate.IsActive) { - throw new FileNotFoundException("The message template '{0}' does not exist.".FormatInvariant(ctx.MessageTemplateName)); + ctx.MessageTemplate = null; } } - if (ctx.EmailAccount == null) + if (ctx.EmailAccount == null && ctx.MessageTemplate != null) { ctx.EmailAccount = GetEmailAccountOfMessageTemplate(ctx.MessageTemplate, ctx.Language.Id); } @@ -376,20 +383,12 @@ private void ValidateMessageContext(MessageContext ctx, ref object[] modelParts) parts = bagParts.Concat(parts.Except(bagParts)); } - modelParts = parts.ToArray(); - } - - protected MessageTemplate GetActiveMessageTemplate(string messageTemplateName, int storeId) - { - var messageTemplate = _messageTemplateService.GetMessageTemplateByName(messageTemplateName, storeId); - if (messageTemplate == null || !messageTemplate.IsActive) - return null; - - return messageTemplate; + modelParts = parts.Where(x => x != null).ToArray(); } protected EmailAccount GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate, int languageId) { + // Note that the email account to be used can be specified separately for each language, that's why we use GetLocalized here. var accountId = messageTemplate.GetLocalized(x => x.EmailAccountId, languageId); var account = _emailAccountService.GetEmailAccountById(accountId); @@ -434,7 +433,7 @@ public virtual object[] GetTestModels(MessageContext messageContext) var factories = new Dictionary>(StringComparer.OrdinalIgnoreCase) { { "BlogComment", () => GetRandomEntity(x => true) }, - { "Product", () => GetRandomEntity(x => !x.Deleted && x.VisibleIndividually && x.Published) }, + { "Product", () => GetRandomEntity(x => !x.Deleted && !x.IsSystemProduct && x.VisibleIndividually && x.Published) }, { "Customer", () => GetRandomEntity(x => !x.Deleted && !x.IsSystemAccount && !string.IsNullOrEmpty(x.Email)) }, { "Order", () => GetRandomEntity(x => !x.Deleted) }, { "Shipment", () => GetRandomEntity(x => !x.Order.Deleted) }, @@ -448,8 +447,9 @@ public virtual object[] GetTestModels(MessageContext messageContext) { "ForumPost", () => GetRandomEntity(x => true) }, { "PrivateMessage", () => GetRandomEntity(x => true) }, { "GiftCard", () => GetRandomEntity(x => true) }, - { "ProductReview", () => GetRandomEntity(x => !x.Product.Deleted && x.Product.VisibleIndividually && x.Product.Published) }, - { "NewsComment", () => GetRandomEntity(x => x.NewsItem.Published) } + { "ProductReview", () => GetRandomEntity(x => !x.Product.Deleted && !x.Product.IsSystemProduct && x.Product.VisibleIndividually && x.Product.Published) }, + { "NewsComment", () => GetRandomEntity(x => x.NewsItem.Published) }, + { "WalletHistory", () => GetRandomEntity(x => true) } }; var modelNames = messageContext.MessageTemplate.ModelTypes @@ -626,7 +626,7 @@ private object GetModelFromExpression(string expression, IDictionary 0) { // Fetch a random one - var skip = new Random().Next(count - 1); + var skip = new Random().Next(count); result = query.OrderBy(x => x.Id).Skip(skip).FirstOrDefault(); } else diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs index f939392935..b328ee6563 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.OrderParts.cs @@ -3,6 +3,8 @@ using System.Dynamic; using System.Linq; using SmartStore.ComponentModel; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; @@ -57,7 +59,10 @@ protected virtual object CreateModelPart(Order part, MessageContext messageConte d.ID = part.Id; d.Billing = CreateModelPart(part.BillingAddress, messageContext); - d.Shipping = part.ShippingAddress?.IsPostalDataEqual(part.BillingAddress) == true ? null : CreateModelPart(part.ShippingAddress, messageContext); + if (part.ShippingAddress != null) + { + d.Shipping = part.ShippingAddress.IsPostalDataEqual(part.BillingAddress) == true ? null : CreateModelPart(part.ShippingAddress, messageContext); + } d.CustomerEmail = part.BillingAddress.Email.NullEmpty(); d.CustomerComment = part.CustomerOrderComment.NullEmpty(); d.Disclaimer = GetTopic("Disclaimer", messageContext); @@ -110,10 +115,10 @@ protected virtual object CreateOrderTotalsPart(Order order, MessageContext messa var taxSettings = _services.Settings.LoadSetting(messageContext.Store.Id); var taxRates = new SortedDictionary(); - string cusTaxTotal = string.Empty; - string cusDiscount = string.Empty; - string cusRounding = string.Empty; - string cusTotal = string.Empty; + Money cusTaxTotal = null; + Money cusDiscount = null; + Money cusRounding = null; + Money cusTotal = null; var subTotals = GetSubTotals(order, messageContext); @@ -153,9 +158,7 @@ protected virtual object CreateOrderTotalsPart(Order order, MessageContext messa displayTaxRates = taxSettings.DisplayTaxRates && taxRates.Count > 0; displayTax = !displayTaxRates; - var orderTaxInCustomerCurrency = currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); - string taxStr = priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); - cusTaxTotal = taxStr; + cusTaxTotal = FormatPrice(order.OrderTax, order, messageContext); } } @@ -163,33 +166,33 @@ protected virtual object CreateOrderTotalsPart(Order order, MessageContext messa bool dislayDiscount = false; if (order.OrderDiscount > decimal.Zero) { - var orderDiscountInCustomerCurrency = currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); - cusDiscount = priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + cusDiscount = FormatPrice(-order.OrderDiscount, order, messageContext); dislayDiscount = true; } // Total var roundingAmount = decimal.Zero; var orderTotal = order.GetOrderTotalInCustomerCurrency(currencyService, paymentService, out roundingAmount); - cusTotal = priceFormatter.FormatPrice(orderTotal, true, order.CustomerCurrencyCode, false, language); + cusTotal = FormatPrice(orderTotal, order.CustomerCurrencyCode, messageContext); // Rounding if (roundingAmount != decimal.Zero) { - cusRounding = priceFormatter.FormatPrice(roundingAmount, true, order.CustomerCurrencyCode, false, language); + cusRounding = FormatPrice(roundingAmount, order.CustomerCurrencyCode, messageContext); } // Model dynamic m = new ExpandoObject(); - m.SubTotal = subTotals.SubTotal.NullEmpty(); + m.SubTotal = subTotals.SubTotal; m.SubTotalDiscount = subTotals.DisplaySubTotalDiscount ? subTotals.SubTotalDiscount : null; m.Shipping = dislayShipping ? subTotals.ShippingTotal : null; m.Payment = displayPaymentMethodFee ? subTotals.PaymentFee : null; m.Tax = displayTax ? cusTaxTotal : null; m.Discount = dislayDiscount ? cusDiscount : null; - m.RoundingDiff = cusRounding.NullEmpty(); + m.RoundingDiff = cusRounding; m.Total = cusTotal; + m.IsGross = order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax; // TaxRates m.TaxRates = !displayTaxRates ? (object[])null : taxRates.Select(x => @@ -223,42 +226,37 @@ protected virtual object CreateOrderTotalsPart(Order order, MessageContext messa return m; } - private (string SubTotal, string SubTotalDiscount, string ShippingTotal, string PaymentFee, bool DisplaySubTotalDiscount) GetSubTotals(Order order, MessageContext messageContext) + private (Money SubTotal, Money SubTotalDiscount, Money ShippingTotal, Money PaymentFee, bool DisplaySubTotalDiscount) GetSubTotals(Order order, MessageContext messageContext) { var language = messageContext.Language; var currencyService = _services.Resolve(); var priceFormatter = _services.Resolve(); - string cusSubTotal = string.Empty; - string cusSubTotalDiscount = string.Empty; - string cusShipTotal = string.Empty; - string cusPaymentMethodFee = string.Empty; - bool dislaySubTotalDiscount = false; + var isNet = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax; - var subTotal = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax ? order.OrderSubtotalExclTax : order.OrderSubtotalInclTax; - var subTotalDiscount = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax ? order.OrderSubTotalDiscountExclTax : order.OrderSubTotalDiscountInclTax; - var shipping = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax ? order.OrderShippingExclTax : order.OrderShippingInclTax; - var payment = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax ? order.PaymentMethodAdditionalFeeExclTax : order.PaymentMethodAdditionalFeeInclTax; + var subTotal = isNet ? order.OrderSubtotalExclTax : order.OrderSubtotalInclTax; + var subTotalDiscount = isNet ? order.OrderSubTotalDiscountExclTax : order.OrderSubTotalDiscountInclTax; + var shipping = isNet ? order.OrderShippingExclTax : order.OrderShippingInclTax; + var payment = isNet ? order.PaymentMethodAdditionalFeeExclTax : order.PaymentMethodAdditionalFeeInclTax; // Subtotal - cusSubTotal = FormatPrice(subTotal, order, messageContext); + var cusSubTotal = FormatPrice(subTotal, order, messageContext); + + // Shipping + var cusShipTotal = FormatPrice(shipping, order, messageContext); + + // Payment method additional fee + var cusPaymentMethodFee = FormatPrice(payment, order, messageContext); // Discount (applied to order subtotal) - var orderSubTotalDiscount = currencyService.ConvertCurrency(subTotalDiscount, order.CurrencyRate); - if (orderSubTotalDiscount > decimal.Zero) + Money cusSubTotalDiscount = null; + bool dislaySubTotalDiscount = false; + if (subTotalDiscount > decimal.Zero) { - cusSubTotalDiscount = priceFormatter.FormatPrice(-orderSubTotalDiscount, true, order.CustomerCurrencyCode, language, false, false); + cusSubTotalDiscount = FormatPrice(-subTotalDiscount, order, messageContext); dislaySubTotalDiscount = true; } - // Shipping - var orderShipping = currencyService.ConvertCurrency(shipping, order.CurrencyRate); - cusShipTotal = priceFormatter.FormatShippingPrice(orderShipping, true, order.CustomerCurrencyCode, language, false, false); - - // Payment method additional fee - var paymentMethodAdditionalFee = currencyService.ConvertCurrency(payment, order.CurrencyRate); - cusPaymentMethodFee = priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, order.CustomerCurrencyCode, language, false, false); - return (cusSubTotal, cusSubTotalDiscount, cusShipTotal, cusPaymentMethodFee, dislaySubTotalDiscount); } @@ -269,11 +267,34 @@ protected virtual object CreateModelPart(OrderItem part, MessageContext messageC var productAttributeParser = _services.Resolve(); var downloadService = _services.Resolve(); - var order = part.Order; + var deliveryTimeService = _services.Resolve(); + var order = part.Order; var isNet = order.CustomerTaxDisplayType == TaxDisplayType.ExcludingTax; var product = part.Product; product.MergeWithCombination(part.AttributesXml, productAttributeParser); + // Bundle items. + object bundleItems = null; + if (product.ProductType == ProductType.BundledProduct && part.BundleData.HasValue()) + { + var bundleData = part.GetBundleData(); + if (bundleData.Any()) + { + var productService = _services.Resolve(); + var products = productService.GetProductsByIds(bundleData.Select(x => x.ProductId).ToArray()); + var productsDic = products.ToDictionarySafe(x => x.Id, x => x); + + bundleItems = bundleData + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + productsDic.TryGetValue(x.ProductId, out Product bundleItemProduct); + return CreateModelPart(x, part, bundleItemProduct, messageContext); + }) + .ToList(); + } + } + var m = new Dictionary { { "DownloadUrl", !downloadService.IsDownloadAllowed(part) ? null : BuildActionUrl("GetDownload", "Download", new { id = part.OrderItemGuid, area = "" }, messageContext) }, @@ -283,10 +304,48 @@ protected virtual object CreateModelPart(OrderItem part, MessageContext messageC { "Qty", part.Quantity }, { "UnitPrice", FormatPrice(isNet ? part.UnitPriceExclTax : part.UnitPriceInclTax, part.Order, messageContext) }, { "LineTotal", FormatPrice(isNet ? part.PriceExclTax : part.PriceInclTax, part.Order, messageContext) }, + { "Product", CreateModelPart(product, messageContext, part.AttributesXml) }, + { "BundleItems", bundleItems }, + { "IsGross", !isNet }, + { "DisplayDeliveryTime", part.DisplayDeliveryTime }, + }; + + if (part.DeliveryTimeId.HasValue) + { + if (deliveryTimeService.GetDeliveryTimeById(part.DeliveryTimeId ?? 0) is DeliveryTime dt) + { + m["DeliveryTime"] = new Dictionary + { + { "Color", dt.ColorHexValue }, + { "Name", dt.GetLocalized(x => x.Name, messageContext.Language).Value }, + }; + } + } + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(ProductBundleItemOrderData part, OrderItem orderItem, Product product, MessageContext messageContext) + { + Guard.NotNull(part, nameof(part)); + Guard.NotNull(orderItem, nameof(orderItem)); + Guard.NotNull(product, nameof(product)); + Guard.NotNull(messageContext, nameof(messageContext)); + + var priceWithDiscount = FormatPrice(part.PriceWithDiscount, orderItem.Order, messageContext); + + var m = new Dictionary + { + { "AttributeDescription", part.AttributesInfo.NullEmpty() }, + { "Quantity", part.Quantity > 1 && part.PerItemShoppingCart ? part.Quantity.ToString() : null }, + { "PerItemShoppingCart", part.PerItemShoppingCart }, + { "PriceWithDiscount", priceWithDiscount }, { "Product", CreateModelPart(product, messageContext, part.AttributesXml) } }; - PublishModelPartCreatedEvent(part, m); + PublishModelPartCreatedEvent(part, m); return m; } @@ -395,6 +454,7 @@ protected virtual object CreateModelPart(ReturnRequest part, MessageContext mess { "CustomerComments", HtmlUtils.FormatText(part.CustomerComments, true, false, false, false, false, false).NullEmpty() }, { "StaffNotes", HtmlUtils.FormatText(part.StaffNotes, true, false, false, false, false, false).NullEmpty() }, { "Quantity", part.Quantity }, + { "RefundToWallet", part.RefundToWallet }, { "Url", BuildActionUrl("Edit", "ReturnRequest", new { id = part.Id, area = "admin" }, messageContext) } }; diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs index 5872aff5ff..9e5a8856e2 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.Utils.cs @@ -12,33 +12,42 @@ using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Core.Domain.Orders; -using System.Text; -using SmartStore.Core.Domain.Common; using SmartStore.Core.Html; using SmartStore.Utilities; +using System.Collections.Generic; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core; namespace SmartStore.Services.Messages { public partial class MessageModelProvider { + private void ApplyCustomerContentPart(IDictionary model, CustomerContent content, MessageContext ctx) + { + model["CustomerId"] = content.CustomerId; + model["IpAddress"] = content.IpAddress; + model["CreatedOn"] = ToUserDate(content.CreatedOnUtc, ctx); + model["UpdatedOn"] = ToUserDate(content.UpdatedOnUtc, ctx); + } + private string BuildUrl(string url, MessageContext ctx) { - return ctx.BaseUri.ToString().TrimEnd('/') + url; + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + url.EnsureStartsWith("/"); } private string BuildRouteUrl(object routeValues, MessageContext ctx) { - return ctx.BaseUri.ToString().TrimEnd('/') + _urlHelper.RouteUrl(routeValues); + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.RouteUrl(routeValues); } private string BuildRouteUrl(string routeName, object routeValues, MessageContext ctx) { - return ctx.BaseUri.ToString().TrimEnd('/') + _urlHelper.RouteUrl(routeName, routeValues); + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.RouteUrl(routeName, routeValues); } private string BuildActionUrl(string action, string controller, object routeValues, MessageContext ctx) { - return ctx.BaseUri.ToString().TrimEnd('/') + _urlHelper.Action(action, controller, routeValues); + return ctx.BaseUri.GetLeftPart(UriPartial.Authority) + _urlHelper.Action(action, controller, routeValues); } private void PublishModelPartCreatedEvent(T source, dynamic part) where T : class @@ -67,14 +76,9 @@ private object GetTopic(string topicSystemName, MessageContext ctx) var topicService = _services.Resolve(); // Load by store - var topic = topicService.GetTopicBySystemName(topicSystemName, ctx.Store.Id); - if (topic == null) - { - // Not found. Let's find topic assigned to all stores - topic = topicService.GetTopicBySystemName(topicSystemName, 0); - } + var topic = topicService.GetTopicBySystemName(topicSystemName, ctx.StoreId ?? 0, false); - var body = topic?.GetLocalized(x => x.Body, ctx.Language.Id); + string body = topic?.GetLocalized(x => x.Body, ctx.Language); if (body.HasValue()) { body = HtmlUtils.RelativizeFontSizes(body); @@ -82,7 +86,7 @@ private object GetTopic(string topicSystemName, MessageContext ctx) return new { - Title = topic?.GetLocalized(x => x.Title, ctx.Language.Id).NullEmpty(), + Title = topic?.GetLocalized(x => x.Title, ctx.Language).Value.NullEmpty(), Body = body.NullEmpty() }; } @@ -108,18 +112,38 @@ private string GetBoolResource(bool value, MessageContext ctx) _services.DateTimeHelper.GetCustomerTimeZone(messageContext.Customer)); } - private string FormatPrice(decimal price, Order order, MessageContext messageContext) + private Money FormatPrice(decimal price, Order order, MessageContext messageContext) + { + return FormatPrice(price, order.CustomerCurrencyCode, messageContext, order.CurrencyRate); + } + + private Money FormatPrice(decimal price, MessageContext messageContext, decimal exchangeRate = 1) { - return FormatPrice(price, order.CurrencyRate, order.CustomerCurrencyCode, messageContext); + return FormatPrice(price, (Currency)null, messageContext, exchangeRate); } - private string FormatPrice(decimal price, decimal currencyRate, string customerCurrencyCode, MessageContext messageContext) + private Money FormatPrice(decimal price, string currencyCode, MessageContext messageContext, decimal exchangeRate = 1) { - var language = messageContext.Language; - var currencyService = _services.Resolve(); - var priceFormatter = _services.Resolve(); + return FormatPrice( + price, + _services.Resolve().GetCurrencyByCode(currencyCode) ?? new Currency { CurrencyCode = currencyCode }, + messageContext, + exchangeRate); + } + + private Money FormatPrice(decimal price, Currency currency, MessageContext messageContext, decimal exchangeRate = 1) + { + if (exchangeRate != 1) + { + price = _services.Resolve().ConvertCurrency(price, exchangeRate); + } + + if (currency == null) + { + currency = _services.Resolve().WorkingCurrency; + } - return priceFormatter.FormatPrice(currencyService.ConvertCurrency(price, currencyRate), true, customerCurrencyCode, false, language); + return new Money(price, currency); } private PictureInfo GetPictureFor(Product product, string attributesXml) diff --git a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs index d1f79a9a4d..ad818b0636 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageModelProvider.cs @@ -1,11 +1,10 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Dynamic; using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Web; using System.Web.Mvc; using SmartStore.Collections; using SmartStore.ComponentModel; @@ -20,6 +19,7 @@ using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Polls; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; @@ -94,6 +94,7 @@ public virtual void AddGlobalModelParts(MessageContext messageContext) { "TemplateName", messageContext.MessageTemplate.Name }, { "LanguageId", messageContext.Language.Id }, { "LanguageCulture", messageContext.Language.LanguageCulture }, + { "LanguageRtl", messageContext.Language.Rtl }, { "BaseUrl", messageContext.BaseUri.ToString() } }; @@ -108,6 +109,88 @@ public virtual void AddGlobalModelParts(MessageContext messageContext) model["Store"] = CreateModelPart(messageContext.Store, messageContext); } + public object CreateModelPart(object part, bool ignoreNullMembers, params string[] ignoreMemberNames) + { + Guard.NotNull(part, nameof(part)); + + var store = _services.StoreContext.CurrentStore; + var messageContext = new MessageContext + { + Language = _services.WorkContext.WorkingLanguage, + Store = store, + BaseUri = new Uri(_services.StoreService.GetHost(store)), + Model = new TemplateModel() + }; + + if (part is Customer x) + { + // This case is not handled in AddModelPart core method. + messageContext.Customer = x; + messageContext.Model["Part"] = CreateModelPart(x, messageContext); + } + else + { + messageContext.Customer = _services.WorkContext.CurrentCustomer; + AddModelPart(part, messageContext, "Part"); + } + + object result = null; + + if (messageContext.Model.Any()) + { + result = messageContext.Model.FirstOrDefault().Value; + + if (result is IDictionary dict) + { + SanitizeModelDictionary(dict, ignoreNullMembers, ignoreMemberNames); + } + } + + return result; + } + + private void SanitizeModelDictionary(IDictionary dict, bool ignoreNullMembers, params string[] ignoreMemberNames) + { + if (ignoreNullMembers || ignoreMemberNames.Length > 0) + { + foreach (var key in dict.Keys.ToArray()) + { + var expando = dict as HybridExpando; + var value = dict[key]; + + if ((ignoreNullMembers && value == null) || ignoreMemberNames.Contains(key)) + { + if (expando != null) + expando.Override(key, null); // INFO: we cannot remove entries from HybridExpando + else + dict.Remove(key); + continue; + } + + if (value != null && value.GetType().IsSequenceType()) + { + var ignoreMemberNames2 = ignoreMemberNames + .Where(x => x.StartsWith(key + ".", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Substring(key.Length + 1)) + .ToArray(); + + if (value is IDictionary dict2) + { + SanitizeModelDictionary(dict2, ignoreNullMembers, ignoreMemberNames2); + } + else + { + var list = ((IEnumerable)value).OfType>(); + foreach (var dict3 in list) + { + SanitizeModelDictionary(dict3, ignoreNullMembers, ignoreMemberNames2); + } + } + } + } + } + } + public virtual void AddModelPart(object part, MessageContext messageContext, string name = null) { Guard.NotNull(part, nameof(part)); @@ -134,7 +217,7 @@ public virtual void AddModelPart(object part, MessageContext messageContext, str modelPart = CreateModelPart(x, messageContext); break; case Customer x: - //modelPart = CreateModelPart(x, messageContext); + modelPart = CreateModelPart(x, messageContext); break; case Address x: modelPart = CreateModelPart(x, messageContext); @@ -181,14 +264,26 @@ public virtual void AddModelPart(object part, MessageContext messageContext, str case PrivateMessage x: modelPart = CreateModelPart(x, messageContext); break; - //case BackInStockSubscription x: - // modelPart = CreateModelPart(x, messageContext); - // break; + case IEnumerable x: + modelPart = CreateModelPart(x, messageContext); + break; + case PollVotingRecord x: + modelPart = CreateModelPart(x, messageContext); + break; + case ProductReviewHelpfulness x: + modelPart = CreateModelPart(x, messageContext); + break; + case ForumSubscription x: + modelPart = CreateModelPart(x, messageContext); + break; + case BackInStockSubscription x: + modelPart = CreateModelPart(x, messageContext); + break; default: var partType = part.GetType(); modelPart = part; - if (!messageContext.TestMode && partType.IsPlainObjectType() && !partType.IsAnonymous()) + if (partType.IsPlainObjectType() && !partType.IsAnonymous()) { var evt = new MessageModelPartMappingEvent(part); _services.EventPublisher.Publish(evt); @@ -389,9 +484,12 @@ protected virtual object CreateModelPart(Store part, MessageContext messageConte { "Copyright", T("Content.CopyrightNotice", messageContext.Language.Id, DateTime.Now.Year.ToString(), part.Name).Text } }; - PublishModelPartCreatedEvent(part, m); + var he = new HybridExpando(true); + he.Merge(m, true); - return m; + PublishModelPartCreatedEvent(part, he); + + return he; } protected virtual object CreateModelPart(PictureInfo part, MessageContext messageContext, @@ -444,11 +542,13 @@ protected virtual object CreateModelPart(Product part, MessageContext messageCon var productUrlHelper = _services.Resolve(); var currency = _services.WorkContext.WorkingCurrency; - var additionalShippingCharge = _services.Resolve().ConvertFromPrimaryStoreCurrency(part.AdditionalShippingCharge, currency); - var additionalShippingChargeFormatted = _services.Resolve().FormatPrice(additionalShippingCharge, false, currency.CurrencyCode, false, messageContext.Language); - var url = productUrlHelper.GetProductUrl(part.Id, part.GetSeName(messageContext.Language.Id), attributesXml); - var pictureInfo = GetPictureFor(part, null); - var name = part.GetLocalized(x => x.Name, messageContext.Language.Id); + var additionalShippingCharge = new Money( + _services.Resolve().ConvertFromPrimaryStoreCurrency(part.AdditionalShippingCharge, currency), + currency, + true); + var url = BuildUrl(productUrlHelper.GetProductUrl(part.Id, part.GetSeName(messageContext.Language.Id), attributesXml), messageContext); + var pictureInfo = GetPictureFor(part, attributesXml); + var name = part.GetLocalized(x => x.Name, messageContext.Language.Id).Value; var alt = T("Media.Product.ImageAlternateTextFormat", messageContext.Language.Id, name).Text; var m = new Dictionary @@ -456,9 +556,9 @@ protected virtual object CreateModelPart(Product part, MessageContext messageCon { "Id", part.Id }, { "Sku", catalogSettings.ShowProductSku ? part.Sku : null }, { "Name", name }, - { "Description", part.GetLocalized(x => x.ShortDescription, messageContext.Language.Id).NullEmpty() }, + { "Description", part.GetLocalized(x => x.ShortDescription, messageContext.Language).Value.NullEmpty() }, { "StockQuantity", part.StockQuantity }, - { "AdditionalShippingCharge", additionalShippingChargeFormatted.NullEmpty() }, + { "AdditionalShippingCharge", additionalShippingCharge }, { "Url", url }, { "Thumbnail", CreateModelPart(pictureInfo, messageContext, url, mediaSettings.MessageProductThumbPictureSize, new Size(50, 50), alt) }, { "ThumbnailLg", CreateModelPart(pictureInfo, messageContext, url, mediaSettings.ProductThumbPictureSize, new Size(120, 120), alt) }, @@ -473,14 +573,14 @@ protected virtual object CreateModelPart(Product part, MessageContext messageCon m["DeliveryTime"] = new Dictionary { { "Color", dt.ColorHexValue }, - { "Name", dt.GetLocalized(x => x.Name, messageContext.Language.Id) }, + { "Name", dt.GetLocalized(x => x.Name, messageContext.Language).Value }, }; } } if (quantityUnitService.GetQuantityUnitById(part.QuantityUnitId) is QuantityUnit qu) { - m["QtyUnit"] = qu.GetLocalized(x => x.Name, messageContext.Language.Id); + m["QtyUnit"] = qu.GetLocalized(x => x.Name, messageContext.Language).Value; } PublishModelPartCreatedEvent(part, m); @@ -516,17 +616,17 @@ protected virtual object CreateModelPart(Customer part, MessageContext messageCo ["FullName"] = GetDisplayNameForCustomer(part).NullEmpty(), ["VatNumber"] = part.GetAttribute(SystemCustomerAttributeNames.VatNumber).NullEmpty(), ["VatNumberStatus"] = part.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId).GetLocalizedEnum(_services.Localization, messageContext.Language.Id).NullEmpty(), - ["CustomerNumber"] = part.GetAttribute(SystemCustomerAttributeNames.CustomerNumber).NullEmpty(), + ["CustomerNumber"] = part.CustomerNumber.NullEmpty(), ["IsRegistered"] = part.IsRegistered(), // URLs ["WishlistUrl"] = BuildRouteUrl("Wishlist", new { customerGuid = part.CustomerGuid }, messageContext), ["EditUrl"] = BuildActionUrl("Edit", "Customer", new { id = part.Id, area = "admin" }, messageContext), ["PasswordRecoveryURL"] = pwdRecoveryToken == null ? null : BuildActionUrl("passwordrecoveryconfirm", "customer", - new { token = part.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken), email = email, area = "" }, + new { token = part.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken), email, area = "" }, messageContext), ["AccountActivationURL"] = accountActivationToken == null ? null : BuildActionUrl("activation", "customer", - new { token = part.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), email = email, area = "" }, + new { token = part.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken), email, area = "" }, messageContext), // Addresses @@ -535,7 +635,7 @@ protected virtual object CreateModelPart(Customer part, MessageContext messageCo // Reward Points ["RewardPointsAmount"] = rewardPointsAmount, - ["RewardPointsBalance"] = _services.Resolve().FormatPrice(rewardPointsAmount, true, false), + ["RewardPointsBalance"] = FormatPrice(rewardPointsAmount, messageContext), ["RewardPointsHistory"] = part.RewardPointsHistory.Count == 0 ? null : part.RewardPointsHistory.Select(x => CreateModelPart(x, messageContext)).ToList(), }; @@ -556,7 +656,7 @@ protected virtual object CreateModelPart(GiftCard part, MessageContext messageCo { "SenderEmail", part.SenderEmail.NullEmpty() }, { "RecipientName", part.RecipientName.NullEmpty() }, { "RecipientEmail", part.RecipientEmail.NullEmpty() }, - { "Amount", _services.Resolve().FormatPrice(part.Amount, true, false) }, + { "Amount", FormatPrice(part.Amount, messageContext) }, { "CouponCode", part.GiftCardCouponCode.NullEmpty() } }; @@ -569,12 +669,11 @@ protected virtual object CreateModelPart(GiftCard part, MessageContext messageCo m["Message"] = message; // RemainingAmount - var remainingAmount = (string)null; + Money remainingAmount = null; var order = part?.PurchasedWithOrderItem?.Order; if (order != null) { - var amount = _services.Resolve().ConvertCurrency(part.GetGiftCardRemainingAmount(), order.CurrencyRate); - remainingAmount = _services.Resolve().FormatPrice(amount, true, false); + remainingAmount = FormatPrice(part.GetGiftCardRemainingAmount(), order, messageContext); } m["RemainingAmount"] = remainingAmount; @@ -616,7 +715,7 @@ protected virtual object CreateModelPart(Campaign part, MessageContext messageCo Guard.NotNull(part, nameof(part)); var protocol = messageContext.BaseUri.Scheme; - var host = messageContext.BaseUri.Host; + var host = messageContext.BaseUri.Authority + messageContext.BaseUri.AbsolutePath; var body = HtmlUtils.RelativizeFontSizes(part.Body.EmptyNull()); // We must render the body separately @@ -652,6 +751,44 @@ protected virtual object CreateModelPart(ProductReview part, MessageContext mess return m; } + protected virtual object CreateModelPart(ProductReviewHelpfulness part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "ProductReviewId", part.ProductReviewId }, + { "ReviewTitle", part.ProductReview.Title }, + { "WasHelpful", part.WasHelpful } + }; + + ApplyCustomerContentPart(m, part, messageContext); + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(PollVotingRecord part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "PollAnswerId", part.PollAnswerId }, + { "PollAnswerName", part.PollAnswer.Name }, + { "PollId", part.PollAnswer.PollId } + }; + + ApplyCustomerContentPart(m, part, messageContext); + + PublishModelPartCreatedEvent(part, m); + + return m; + } + protected virtual object CreateModelPart(PrivateMessage part, MessageContext messageContext) { Guard.NotNull(messageContext, nameof(messageContext)); @@ -757,8 +894,8 @@ protected virtual object CreateModelPart(Forum part, MessageContext messageConte var m = new Dictionary { - { "Name", part.GetLocalized(x => x.Name, messageContext.Language.Id).NullEmpty() }, - { "GroupName", part.ForumGroup?.GetLocalized(x => x.Name, messageContext.Language.Id).NullEmpty() }, + { "Name", part.GetLocalized(x => x.Name, messageContext.Language).Value.NullEmpty() }, + { "GroupName", part.ForumGroup?.GetLocalized(x => x.Name, messageContext.Language)?.Value.NullEmpty() }, { "NumPosts", part.NumPosts }, { "NumTopics", part.NumTopics }, { "Url", BuildRouteUrl("ForumSlug", new { id = part.Id, slug = part.GetSeName(messageContext.Language.Id) }, messageContext) }, @@ -786,8 +923,8 @@ protected virtual object CreateModelPart(Address part, MessageContext messageCon var street2 = settings.StreetAddress2Enabled ? part.Address2 : null; var zip = settings.ZipPostalCodeEnabled ? part.ZipPostalCode : null; var city = settings.CityEnabled ? part.City : null; - var country = settings.CountryEnabled ? part.Country?.GetLocalized(x => x.Name, languageId ?? 0).NullEmpty() : null; - var state = settings.StateProvinceEnabled ? part.StateProvince?.GetLocalized(x => x.Name, languageId ?? 0).NullEmpty() : null; + var country = settings.CountryEnabled ? part.Country?.GetLocalized(x => x.Name, languageId ?? 0)?.Value.NullEmpty() : null; + var state = settings.StateProvinceEnabled ? part.StateProvince?.GetLocalized(x => x.Name, languageId ?? 0)?.Value.NullEmpty() : null; var m = new Dictionary { @@ -843,6 +980,60 @@ protected virtual object CreateModelPart(RewardPointsHistory part, MessageContex return m; } + protected virtual object CreateModelPart(IEnumerable part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary(); + + foreach (var attr in part) + { + m[attr.Key] = attr.Value; + } + + PublishModelPartCreatedEvent>(part, m); + + return m; + } + + protected virtual object CreateModelPart(ForumSubscription part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "SubscriptionGuid", part.SubscriptionGuid }, + { "CustomerId", part.CustomerId }, + { "ForumId", part.ForumId }, + { "TopicId", part.TopicId }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(BackInStockSubscription part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "StoreId", part.StoreId }, + { "CustomerId", part.CustomerId }, + { "ProductId", part.ProductId }, + { "CreatedOn", ToUserDate(part.CreatedOnUtc, messageContext) } + }; + + PublishModelPartCreatedEvent(part, m); + + return m; + } + #endregion #region Model Tree diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs index 7d4e91b30f..e9e25c6677 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTemplateService.cs @@ -34,23 +34,22 @@ public MessageTemplateService( IRepository messageTemplateRepository, IEventPublisher eventPublisher) { - this._requestCache = requestCache; - this._storeMappingRepository = storeMappingRepository; - this._languageService = languageService; - this._localizedEntityService = localizedEntityService; - this._storeMappingService = storeMappingService; - this._messageTemplateRepository = messageTemplateRepository; - this._eventPublisher = eventPublisher; - - this.QuerySettings = DbQuerySettings.Default; + _requestCache = requestCache; + _storeMappingRepository = storeMappingRepository; + _languageService = languageService; + _localizedEntityService = localizedEntityService; + _storeMappingService = storeMappingService; + _messageTemplateRepository = messageTemplateRepository; + _eventPublisher = eventPublisher; + + QuerySettings = DbQuerySettings.Default; } public DbQuerySettings QuerySettings { get; set; } public virtual void DeleteMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); _messageTemplateRepository.Delete(messageTemplate); @@ -59,20 +58,18 @@ public virtual void DeleteMessageTemplate(MessageTemplate messageTemplate) public virtual void InsertMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); - _messageTemplateRepository.Insert(messageTemplate); + _messageTemplateRepository.Insert(messageTemplate); _requestCache.RemoveByPattern(MESSAGETEMPLATES_PATTERN_KEY); } public virtual void UpdateMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); - _messageTemplateRepository.Update(messageTemplate); + _messageTemplateRepository.Update(messageTemplate); _requestCache.RemoveByPattern(MESSAGETEMPLATES_PATTERN_KEY); } @@ -87,8 +84,7 @@ public virtual MessageTemplate GetMessageTemplateById(int messageTemplateId) public virtual MessageTemplate GetMessageTemplateByName(string messageTemplateName, int storeId) { - if (string.IsNullOrWhiteSpace(messageTemplateName)) - throw new ArgumentException("messageTemplateName"); + Guard.NotEmpty(messageTemplateName, nameof(messageTemplateName)); string key = string.Format(MESSAGETEMPLATES_BY_NAME_KEY, messageTemplateName, storeId); return _requestCache.Get(key, () => @@ -117,7 +113,7 @@ public virtual IList GetAllMessageTemplates(int storeId) var query = _messageTemplateRepository.Table; query = query.OrderBy(t => t.Name); - //Store mapping + // Store mapping if (storeId > 0 && !QuerySettings.IgnoreMultiStore) { query = from t in query @@ -127,7 +123,7 @@ from sm in t_sm.DefaultIfEmpty() where !t.LimitedToStores || storeId == sm.StoreId select t; - //only distinct items (group by ID) + // Only distinct items (group by ID) query = from t in query group t by t.Id into tGroup orderby tGroup.Key @@ -141,8 +137,7 @@ orderby tGroup.Key public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTemplate) { - if (messageTemplate == null) - throw new ArgumentNullException("messageTemplate"); + Guard.NotNull(messageTemplate, nameof(messageTemplate)); var mtCopy = new MessageTemplate { @@ -165,19 +160,19 @@ public virtual MessageTemplate CopyMessageTemplate(MessageTemplate messageTempla // localization foreach (var lang in languages) { - var bccEmailAddresses = messageTemplate.GetLocalized(x => x.BccEmailAddresses, lang.Id, false, false); + string bccEmailAddresses = messageTemplate.GetLocalized(x => x.BccEmailAddresses, lang, false, false); if (bccEmailAddresses.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.BccEmailAddresses, bccEmailAddresses, lang.Id); - var subject = messageTemplate.GetLocalized(x => x.Subject, lang.Id, false, false); + string subject = messageTemplate.GetLocalized(x => x.Subject, lang, false, false); if (subject.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.Subject, subject, lang.Id); - var body = messageTemplate.GetLocalized(x => x.Body, lang.Id, false, false); + string body = messageTemplate.GetLocalized(x => x.Body, lang, false, false); if (body.HasValue()) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.Body, subject, lang.Id); - var emailAccountId = messageTemplate.GetLocalized(x => x.EmailAccountId, lang.Id, false, false); + int emailAccountId = messageTemplate.GetLocalized(x => x.EmailAccountId, lang, false, false); if (emailAccountId > 0) _localizedEntityService.SaveLocalizedValue(mtCopy, x => x.EmailAccountId, emailAccountId, lang.Id); } diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index 58a8d28d2f..5dc78f8647 100644 --- a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs @@ -9,18 +9,14 @@ namespace SmartStore.Services.Messages { public class NewsLetterSubscriptionService : INewsLetterSubscriptionService { - private readonly IEventPublisher _eventPublisher; - private readonly IDbContext _context; private readonly IRepository _subscriptionRepository; + private readonly ICommonServices _services; - public NewsLetterSubscriptionService(IDbContext context, - IRepository subscriptionRepository, - IEventPublisher eventPublisher) + public NewsLetterSubscriptionService(IRepository subscriptionRepository, ICommonServices services) { - _context = context; _subscriptionRepository = subscriptionRepository; - _eventPublisher = eventPublisher; - } + _services = services; + } /// /// Inserts a newsletter subscription @@ -29,23 +25,20 @@ public NewsLetterSubscriptionService(IDbContext context, /// if set to true [publish subscription events]. public void InsertNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true) { - if (newsLetterSubscription == null) - { - throw new ArgumentNullException("newsLetterSubscription"); - } + Guard.NotNull(newsLetterSubscription, nameof(newsLetterSubscription)); if (newsLetterSubscription.StoreId == 0) { throw new SmartException("News letter subscription must be assigned to a valid store."); } - //Handle e-mail + // Handle e-mail newsLetterSubscription.Email = EnsureSubscriberEmailOrThrow(newsLetterSubscription.Email); - //Persist + // Persist _subscriptionRepository.Insert(newsLetterSubscription); - //Publish the subscription event + // Publish the subscription event if (newsLetterSubscription.Active) { PublishSubscriptionEvent(newsLetterSubscription.Email, true, publishSubscriptionEvents); @@ -59,43 +52,40 @@ public void InsertNewsLetterSubscription(NewsLetterSubscription newsLetterSubscr /// if set to true [publish subscription events]. public void UpdateNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true) { - if (newsLetterSubscription == null) - { - throw new ArgumentNullException("newsLetterSubscription"); - } + Guard.NotNull(newsLetterSubscription, nameof(newsLetterSubscription)); if (newsLetterSubscription.StoreId == 0) { throw new SmartException("News letter subscription must be assigned to a valid store."); } - //Handle e-mail + // Handle e-mail newsLetterSubscription.Email = EnsureSubscriberEmailOrThrow(newsLetterSubscription.Email); - //Get original subscription record - var originalSubscription = _context.LoadOriginalCopy(newsLetterSubscription); + // Get original subscription record + var originalSubscription = _services.DbContext.LoadOriginalCopy(newsLetterSubscription); - //Persist + // Persist _subscriptionRepository.Update(newsLetterSubscription); - //Publish the subscription event + // Publish the subscription event if ((originalSubscription.Active == false && newsLetterSubscription.Active) || (newsLetterSubscription.Active && (originalSubscription.Email != newsLetterSubscription.Email))) { - //If the previous entry was false, but this one is true, publish a subscribe. + // If the previous entry was false, but this one is true, publish a subscribe. PublishSubscriptionEvent(newsLetterSubscription.Email, true, publishSubscriptionEvents); } if ((originalSubscription.Active && newsLetterSubscription.Active) && (originalSubscription.Email != newsLetterSubscription.Email)) { - //If the two emails are different publish an unsubscribe. + // If the two emails are different publish an unsubscribe. PublishSubscriptionEvent(originalSubscription.Email, false, publishSubscriptionEvents); } if ((originalSubscription.Active && !newsLetterSubscription.Active)) { - //If the previous entry was true, but this one is false + // If the previous entry was true, but this one is false PublishSubscriptionEvent(originalSubscription.Email, false, publishSubscriptionEvents); } } @@ -107,8 +97,7 @@ public void UpdateNewsLetterSubscription(NewsLetterSubscription newsLetterSubscr /// if set to true [publish subscription events]. public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true) { - if (newsLetterSubscription == null) - throw new ArgumentNullException("newsLetterSubscription"); + Guard.NotNull(newsLetterSubscription, nameof(newsLetterSubscription)); _subscriptionRepository.Delete(newsLetterSubscription); @@ -127,7 +116,10 @@ public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLett { if (add) { - newsletter.Active = true; + if (!newsletter.Active) + { + _services.MessageFactory.SendNewsLetterSubscriptionActivationMessage(newsletter, _services.WorkContext.WorkingLanguage.Id); + } UpdateNewsLetterSubscription(newsletter); result = true; } @@ -141,14 +133,19 @@ public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLett { if (add) { - InsertNewsLetterSubscription(new NewsLetterSubscription + newsletter = new NewsLetterSubscription { NewsLetterSubscriptionGuid = Guid.NewGuid(), Email = email, - Active = true, + Active = false, CreatedOnUtc = DateTime.UtcNow, - StoreId = storeId - }); + StoreId = storeId, + WorkingLanguageId = _services.WorkContext.WorkingLanguage.Id + }; + InsertNewsLetterSubscription(newsletter); + + _services.MessageFactory.SendNewsLetterSubscriptionActivationMessage(newsletter, _services.WorkContext.WorkingLanguage.Id); + result = true; } } @@ -254,13 +251,13 @@ private void PublishSubscriptionEvent(string email, bool isSubscribe, bool publi { if (publishSubscriptionEvents) { - if (isSubscribe) + if (isSubscribe) { - _eventPublisher.Publish(new EmailSubscribedEvent(email)); + _services.EventPublisher.Publish(new EmailSubscribedEvent(email)); } else { - _eventPublisher.Publish(new EmailUnsubscribedEvent(email)); + _services.EventPublisher.Publish(new EmailUnsubscribedEvent(email)); } } } diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs index 9a8029e57f..45da5bef58 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs @@ -96,7 +96,7 @@ public string FormatAttributes(string attributes, if (ca.AttributeControlType == AttributeControlType.MultilineTextbox) { //multiline textbox - var attributeName = ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + string attributeName = ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage); //encode (if required) if (htmlEncode) attributeName = HttpUtility.HtmlEncode(attributeName); @@ -133,7 +133,7 @@ public string FormatAttributes(string attributes, //hyperlinks aren't allowed attributeText = fileName; } - var attributeName = ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id); + string attributeName = ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage); //encode (if required) if (htmlEncode) attributeName = HttpUtility.HtmlEncode(attributeName); @@ -143,7 +143,7 @@ public string FormatAttributes(string attributes, else { //other attributes (textbox, datepicker) - caAttribute = string.Format("{0}: {1}", ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), valueStr); + caAttribute = string.Format("{0}: {1}", ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage), valueStr); //encode (if required) if (htmlEncode) caAttribute = HttpUtility.HtmlEncode(caAttribute); @@ -157,7 +157,7 @@ public string FormatAttributes(string attributes, var caValue = _checkoutAttributeService.GetCheckoutAttributeValueById(caId); if (caValue != null) { - caAttribute = string.Format("{0}: {1}", ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id), caValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage.Id)); + caAttribute = string.Format("{0}: {1}", ca.GetLocalized(a => a.Name, _workContext.WorkingLanguage), caValue.GetLocalized(a => a.Name, _workContext.WorkingLanguage)); if (renderPrices) { decimal priceAdjustmentBase = _taxService.GetCheckoutAttributePrice(caValue, customer); diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs index b5f278c3d5..381a207c40 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderReportService.cs @@ -56,7 +56,14 @@ IList BestSellersReport(int storeId, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, int billingCountryId = 0, int recordsToReturn = 5, int orderBy = 1, bool showHidden = false); - + + /// + /// Gets a the count of purchases for a product + /// + /// Product identifier + /// Purchase count + int GetPurchaseCount(int productId); + /// /// Gets a list of product identifiers purchased by other customers who purchased the above /// diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs index f5c2a08340..3f5ee14bb5 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderTotalCalculationService.cs @@ -67,12 +67,12 @@ decimal GetOrderSubtotalDiscount(Customer customer, /// /// Adjust shipping rate (free shipping, additional charges, discounts) /// - /// Shipping rate to adjust + /// Shipping option /// Cart /// Applied discount /// Adjusted shipping rate - decimal AdjustShippingRate(decimal shippingRate, IList cart, - string shippingMethodName, IList shippingMethods, out Discount appliedDiscount); + decimal AdjustShippingRate(decimal shippingRate, IList cart, + ShippingOption shippingOption, IList shippingMethods, out Discount appliedDiscount); /// /// Gets shopping cart additional shipping charge @@ -153,13 +153,15 @@ decimal GetTaxTotal(IList cart, out SortedDictionary< /// Gets the shopping cart total /// /// Shopping cart - /// A value indicating whether we should ignore reward points (if enabled and a customer is going to use them) + /// A value indicating whether we should ignore reward points (if enabled and a customer is going to use them) /// A value indicating whether we should use payment method additional fee when calculating order total + /// A value indicating whether to ignore a credit balance. /// Shopping cart total. TotalAmount is null if shopping cart total couldn't be calculated now. ShoppingCartTotal GetShoppingCartTotal( IList cart, - bool ignoreRewardPonts = false, - bool usePaymentMethodAdditionalFee = true); + bool ignoreRewardPoints = false, + bool usePaymentMethodAdditionalFee = true, + bool ignoreCreditBalance = false); /// diff --git a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs index fe67125ad6..64b10fc2cb 100644 --- a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs @@ -52,8 +52,9 @@ void DeleteShoppingCartItem( /// Deletes expired shopping cart items /// /// Older than date and time + /// null to delete ALL cart items, or a customer id to only delete items of a single customer. /// Number of deleted items - int DeleteExpiredShoppingCartItems(DateTime olderThanUtc); + int DeleteExpiredShoppingCartItems(DateTime olderThanUtc, int? customerId = null); /// /// Validates required products (products which require other variant to be added to the cart) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 40b07cabe8..492618410a 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -73,12 +73,13 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly LocalizationSettings _localizationSettings; private readonly CurrencySettings _currencySettings; private readonly ShoppingCartSettings _shoppingCartSettings; + private readonly CatalogSettings _catalogSettings; - #endregion + #endregion - #region Ctor + #region Ctor - public OrderProcessingService( + public OrderProcessingService( IOrderService orderService, IWebHelper webHelper, ILocalizationService localizationService, @@ -114,7 +115,8 @@ public OrderProcessingService( TaxSettings taxSettings, LocalizationSettings localizationSettings, CurrencySettings currencySettings, - ShoppingCartSettings shoppingCartSettings) + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings) { _orderService = orderService; _webHelper = webHelper; @@ -152,6 +154,7 @@ public OrderProcessingService( _localizationSettings = localizationSettings; _currencySettings = currencySettings; _shoppingCartSettings = shoppingCartSettings; + _catalogSettings = catalogSettings; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -977,6 +980,7 @@ public virtual PlaceOrderResult PlaceOrder( OrderTotal = cartTotal.TotalAmount.Value, RefundedAmount = decimal.Zero, OrderDiscount = cartTotal.DiscountAmount, + CreditBalance = cartTotal.CreditBalance, CheckoutAttributeDescription = checkoutAttributeDescription, CheckoutAttributesXml = checkoutAttributesXml, CustomerCurrencyCode = customerCurrencyCode, @@ -1029,12 +1033,12 @@ public virtual PlaceOrderResult PlaceOrder( if (!processPaymentRequest.IsRecurringPayment) { - // Move shopping cart items to order products + // Move shopping cart items to order products. foreach (var sc in cart) { sc.Item.Product.MergeWithCombination(sc.Item.AttributesXml); - // Prices + // Prices. decimal taxRate = decimal.Zero; decimal unitPriceTaxRate = decimal.Zero; decimal scUnitPrice = _priceCalculationService.GetUnitPrice(sc, true); @@ -1044,7 +1048,7 @@ public virtual PlaceOrderResult PlaceOrder( decimal scSubTotalInclTax = _taxService.GetProductPrice(sc.Item.Product, scSubTotal, true, customer, out taxRate); decimal scSubTotalExclTax = _taxService.GetProductPrice(sc.Item.Product, scSubTotal, false, customer, out taxRate); - // Discounts + // Discounts. Discount scDiscount = null; decimal discountAmount = _priceCalculationService.GetDiscountAmount(sc, out scDiscount); decimal discountAmountInclTax = _taxService.GetProductPrice(sc.Item.Product, discountAmount, true, customer, out taxRate); @@ -1055,12 +1059,15 @@ public virtual PlaceOrderResult PlaceOrder( appliedDiscounts.Add(scDiscount); } - // Attributes var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Item.Product, sc.Item.AttributesXml, customer); - var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); + var displayDeliveryTime = + _shoppingCartSettings.ShowDeliveryTimes && + sc.Item.Product.DeliveryTimeId.HasValue && + sc.Item.Product.IsShipEnabled && + sc.Item.Product.DisplayDeliveryTimeAccordingToStock(_catalogSettings); - // Dave order item + // Save order item. var orderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), @@ -1080,10 +1087,12 @@ public virtual PlaceOrderResult PlaceOrder( IsDownloadActivated = false, LicenseDownloadId = 0, ItemWeight = itemWeight, - ProductCost = _priceCalculationService.GetProductCost(sc.Item.Product, sc.Item.AttributesXml) + ProductCost = _priceCalculationService.GetProductCost(sc.Item.Product, sc.Item.AttributesXml), + DeliveryTimeId = sc.Item.Product.GetDeliveryTimeIdAccordingToStock(_catalogSettings), + DisplayDeliveryTime = displayDeliveryTime }; - if (sc.Item.Product.ProductType == ProductType.BundledProduct && sc.ChildItems != null) + if (sc.Item.Product.ProductType == ProductType.BundledProduct && sc.ChildItems != null) { var listBundleData = new List(); @@ -1106,10 +1115,13 @@ public virtual PlaceOrderResult PlaceOrder( // Gift cards if (sc.Item.Product.IsGiftCard) { - string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; - - _productAttributeParser.GetGiftCardAttribute(sc.Item.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + _productAttributeParser.GetGiftCardAttribute( + sc.Item.AttributesXml, + out var giftCardRecipientName, + out var giftCardRecipientEmail, + out var giftCardSenderName, + out var giftCardSenderEmail, + out var giftCardMessage); for (int i = 0; i < sc.Item.Quantity; i++) { @@ -1168,7 +1180,9 @@ public virtual PlaceOrderResult PlaceOrder( LicenseDownloadId = 0, ItemWeight = orderItem.ItemWeight, BundleData = orderItem.BundleData, - ProductCost = orderItem.ProductCost + ProductCost = orderItem.ProductCost, + DeliveryTimeId = orderItem.DeliveryTimeId, + DisplayDeliveryTime = orderItem.DisplayDeliveryTime }; order.OrderItems.Add(newOrderItem); _orderService.UpdateOrder(order); @@ -1176,10 +1190,13 @@ public virtual PlaceOrderResult PlaceOrder( // Gift cards if (orderItem.Product.IsGiftCard) { - string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; - - _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + _productAttributeParser.GetGiftCardAttribute( + orderItem.AttributesXml, + out var giftCardRecipientName, + out var giftCardRecipientEmail, + out var giftCardSenderName, + out var giftCardSenderEmail, + out var giftCardMessage); for (int i = 0; i < orderItem.Quantity; i++) { @@ -1304,7 +1321,7 @@ public virtual PlaceOrderResult PlaceOrder( // notes, messages _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPlaced")); - //send email notifications + // send email notifications var msg = _messageFactory.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); if (msg?.Email?.Id != null) { @@ -1323,7 +1340,7 @@ public virtual PlaceOrderResult PlaceOrder( //reset checkout data if (!processPaymentRequest.IsRecurringPayment && !processPaymentRequest.IsMultiOrder) { - _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true, clearRewardPoints: true); + _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, true, true, true, clearCreditBalance: true); } // check for generic attributes to be inserted automatically diff --git a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs index f98d7292f0..6088986a46 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs @@ -215,7 +215,7 @@ join p in _productRepository.Table on orderItem.ProductId equals p.Id (!paymentStatusId.HasValue || paymentStatusId == o.PaymentStatusId) && (!shippingStatusId.HasValue || shippingStatusId == o.ShippingStatusId) && (!o.Deleted) && - (!p.Deleted) && + (!p.Deleted) && (!p.IsSystemProduct) && (billingCountryId == 0 || o.BillingAddress.CountryId == billingCountryId) && (showHidden || p.Published) select orderItem; @@ -264,7 +264,20 @@ group orderItem by orderItem.ProductId into g return result; } - public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, int recordsToReturn = 5, bool showHidden = false) + public virtual int GetPurchaseCount(int productId) + { + if (productId == 0) + throw new ArgumentException("Product ID is not specified"); + + var query = from orderItem in _orderItemRepository.Table + where orderItem.ProductId == productId + group orderItem by orderItem.Id into g + select new { ProductsPurchased = g.Sum(x => x.Quantity) }; + + return query.Select(x => x.ProductsPurchased).FirstOrDefault(); + } + + public virtual int[] GetAlsoPurchasedProductsIds(int storeId, int productId, int recordsToReturn = 5, bool showHidden = false) { if (productId == 0) throw new ArgumentException("Product ID is not specified"); @@ -281,8 +294,8 @@ join p in _productRepository.Table on orderItem.ProductId equals p.Id (showHidden || p.Published) && (!orderItem.Order.Deleted) && (storeId == 0 || orderItem.Order.StoreId == storeId) && - (!p.Deleted) && - (showHidden || p.Published) + (!p.Deleted) && (!p.IsSystemProduct) && + (showHidden || p.Published) select new { orderItem = orderItem, p }; var query3 = from orderItem_p in query2 @@ -318,8 +331,10 @@ group orderItem_p by orderItem_p.p.Id into g public virtual IPagedList ProductsNeverSold(DateTime? startTime, DateTime? endTime, int pageIndex, int pageSize, bool showHidden = false) { - //this inner query should retrieve all purchased order product varint identifiers - var query1 = (from orderItem in _orderItemRepository.Table + var groupedProductId = (int)ProductType.GroupedProduct; + + // This inner query should retrieve all purchased order product varint identifiers. + var query1 = (from orderItem in _orderItemRepository.Table join o in _orderRepository.Table on orderItem.OrderId equals o.Id where (!startTime.HasValue || startTime.Value <= o.CreatedOnUtc) && (!endTime.HasValue || endTime.Value >= o.CreatedOnUtc) && @@ -327,11 +342,12 @@ join o in _orderRepository.Table on orderItem.OrderId equals o.Id select orderItem.ProductId).Distinct(); var query2 = from p in _productRepository.Table - orderby p.Name - where (!query1.Contains(p.Id)) && - (!p.Deleted) && + where !query1.Contains(p.Id) && + p.ProductTypeId != groupedProductId && + !p.Deleted && (showHidden || p.Published) - select p; + orderby p.Name + select p; var products = new PagedList(query2, pageIndex, pageSize); return products; diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 98d7357f15..d2fbbd50e7 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -206,7 +206,7 @@ protected virtual void PrepareAuxiliaryServicesTaxingInfos(IList cart) /// Applied discount /// Adjusted shipping rate public virtual decimal AdjustShippingRate(decimal shippingRate, IList cart, - string shippingMethodName, IList shippingMethods, out Discount appliedDiscount) + ShippingOption shippingOption, IList shippingMethods, out Discount appliedDiscount) { appliedDiscount = null; - //free shipping - if (IsFreeShipping(cart)) - return decimal.Zero; + if (IsFreeShipping(cart)) + { + return decimal.Zero; + } - decimal adjustedRate = decimal.Zero; - decimal bundlePerItemShipping = decimal.Zero; - bool ignoreAdditionalShippingCharge = false; - ShippingMethod shippingMethod; + var adjustedRate = decimal.Zero; + var bundlePerItemShipping = decimal.Zero; + var ignoreAdditionalShippingCharge = false; foreach (var sci in cart) { @@ -695,7 +695,9 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList x.Item.IsShipEnabled && !x.Item.IsFreeShipping)) + { bundlePerItemShipping += shippingRate; + } } } else if (adjustedRate == decimal.Zero) @@ -706,22 +708,25 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList x.Name.IsCaseInsensitiveEqual(shippingMethodName))) != null) + if (shippingOption != null && shippingMethods != null) { - ignoreAdditionalShippingCharge = shippingMethod.IgnoreCharges; + var shippingMethod = shippingMethods.FirstOrDefault(x => x.Id == shippingOption.ShippingMethodId); + if (shippingMethod != null) + { + ignoreAdditionalShippingCharge = shippingMethod.IgnoreCharges; + } } - //additional shipping charges + // Additional shipping charges. if (!ignoreAdditionalShippingCharge) { decimal additionalShippingCharge = GetShoppingCartAdditionalShippingCharge(cart); adjustedRate += additionalShippingCharge; } - //discount + // Discount. var customer = cart.GetCustomer(); - decimal discountAmount = GetShippingDiscount(customer, adjustedRate, out appliedDiscount); + var discountAmount = GetShippingDiscount(customer, adjustedRate, out appliedDiscount); adjustedRate = adjustedRate - discountAmount; if (adjustedRate < decimal.Zero) @@ -1101,8 +1106,9 @@ public virtual decimal GetTaxTotal(IList cart, out So public virtual ShoppingCartTotal GetShoppingCartTotal( IList cart, - bool ignoreRewardPonts = false, - bool usePaymentMethodAdditionalFee = true) + bool ignoreRewardPoints = false, + bool usePaymentMethodAdditionalFee = true, + bool ignoreCreditBalance = false) { var customer = cart.GetCustomer(); var store = _storeContext.CurrentStore; @@ -1214,7 +1220,7 @@ public virtual ShoppingCartTotal GetShoppingCartTotal( var redeemedRewardPointsAmount = decimal.Zero; if (_rewardPointsSettings.Enabled && - !ignoreRewardPonts && customer != null && + !ignoreRewardPoints && customer != null && customer.GetAttribute(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, store.Id)) { var rewardPointsBalance = customer.GetRewardPointsBalance(); @@ -1235,25 +1241,47 @@ public virtual ShoppingCartTotal GetShoppingCartTotal( } } - #endregion + #endregion - if (resultTemp < decimal.Zero) + if (resultTemp < decimal.Zero) { resultTemp = decimal.Zero; } resultTemp = resultTemp.RoundIfEnabledFor(currency); - // Return null if we have errors - var roundingAmount = decimal.Zero; + // Return null if we have errors + var roundingAmount = decimal.Zero; var roundingAmountConverted = decimal.Zero; var orderTotal = shoppingCartShipping.HasValue ? resultTemp : (decimal?)null; var orderTotalConverted = orderTotal; + var appliedCreditBalance = decimal.Zero; - if (orderTotal.HasValue) + if (orderTotal.HasValue) { orderTotal = orderTotal.Value - redeemedRewardPointsAmount; - orderTotal = orderTotal.Value.RoundIfEnabledFor(currency); + + // Credit balance. + if (!ignoreCreditBalance && customer != null && orderTotal > decimal.Zero) + { + var creditBalance = customer.GetAttribute(SystemCustomerAttributeNames.UseCreditBalanceDuringCheckout, _genericAttributeService, store.Id); + if (creditBalance > decimal.Zero) + { + if (creditBalance > orderTotal) + { + // Normalize used amount. + appliedCreditBalance = orderTotal.Value; + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.UseCreditBalanceDuringCheckout, orderTotal.Value, store.Id); + } + else + { + appliedCreditBalance = creditBalance; + } + } + } + + orderTotal = orderTotal.Value - appliedCreditBalance; + orderTotal = orderTotal.Value.RoundIfEnabledFor(currency); orderTotalConverted = _currencyService.ConvertFromPrimaryStoreCurrency(orderTotal.Value, currency, store); @@ -1276,6 +1304,7 @@ public virtual ShoppingCartTotal GetShoppingCartTotal( result.AppliedGiftCards = appliedGiftCards; result.RedeemedRewardPoints = redeemedRewardPoints; result.RedeemedRewardPointsAmount = redeemedRewardPointsAmount; + result.CreditBalance = appliedCreditBalance; result.ConvertedFromPrimaryStoreCurrency.TotalAmount = orderTotalConverted; result.ConvertedFromPrimaryStoreCurrency.RoundingAmount = roundingAmountConverted; diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index 335760c7d9..47e05d3015 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -143,7 +143,7 @@ public virtual List GetCartItems(Customer customer, S return result; } - protected List OrganizeCartItems(IEnumerable cart) + protected virtual List OrganizeCartItems(IEnumerable cart) { var result = new List(); @@ -205,7 +205,7 @@ public virtual void DeleteShoppingCartItem( int cartItemId = shoppingCartItem.Id; // reset checkout data - if (resetCheckoutData) + if (resetCheckoutData && customer != null) { _customerService.ResetCheckoutData(shoppingCartItem.Customer, shoppingCartItem.StoreId); } @@ -217,7 +217,7 @@ public virtual void DeleteShoppingCartItem( _requestCache.RemoveByPattern(CARTITEMS_PATTERN_KEY); // validate checkout attributes - if (ensureOnlyActiveCheckoutAttributes && shoppingCartItem.ShoppingCartType == ShoppingCartType.ShoppingCart) + if (ensureOnlyActiveCheckoutAttributes && shoppingCartItem.ShoppingCartType == ShoppingCartType.ShoppingCart && customer != null) { var cart = GetCartItems(customer, ShoppingCartType.ShoppingCart, storeId); @@ -227,7 +227,7 @@ public virtual void DeleteShoppingCartItem( } // delete child items - if (deleteChildCartItems) + if (deleteChildCartItems && customer != null) { var childCartItems = _sciRepository.Table .Where(x => x.CustomerId == customer.Id && x.ParentItemId != null && x.ParentItemId.Value == cartItemId && x.Id != cartItemId) @@ -253,22 +253,28 @@ public virtual void DeleteShoppingCartItem( } } - public virtual int DeleteExpiredShoppingCartItems(DateTime olderThanUtc) + public virtual int DeleteExpiredShoppingCartItems(DateTime olderThanUtc, int? customerId = null) { var query = from sci in _sciRepository.Table where sci.UpdatedOnUtc < olderThanUtc && sci.ParentItemId == null select sci; + if (customerId.GetValueOrDefault() > 0) + { + query = query.Where(x => x.CustomerId == customerId.Value); + } + var cartItems = query.ToList(); foreach (var parentItem in cartItems) { - var childItems = _sciRepository.Table - .Where(x => x.ParentItemId != null && x.ParentItemId.Value == parentItem.Id && x.Id != parentItem.Id).ToList(); + var childItems = _sciRepository.Table.Where(x => x.ParentItemId == parentItem.Id && x.Id != parentItem.Id).ToList(); foreach (var childItem in childItems) + { _sciRepository.Delete(childItem); + } _sciRepository.Delete(parentItem); } @@ -1008,9 +1014,10 @@ public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart( } } - // price is the same (for products which require customers to enter a price) - var customerEnteredPricesEqual = true; - if (sci.Item.Product.CustomerEntersPrice) + // Products with CustomerEntersPrice are equal if the price is the same. + // But a system product may only be placed once in the shopping cart. + var customerEnteredPricesEqual = true; + if (sci.Item.Product.CustomerEntersPrice && !sci.Item.Product.IsSystemProduct) { customerEnteredPricesEqual = Math.Round(sci.Item.CustomerEnteredPrice, 2) == Math.Round(customerEnteredPrice, 2); } @@ -1165,8 +1172,7 @@ public virtual List AddToCart( public virtual void AddToCart(AddToCartContext ctx) { var customer = ctx.Customer ?? _workContext.CurrentCustomer; - int storeId = ctx.StoreId ?? _storeContext.CurrentStore.Id; - var cart = GetCartItems(customer, ctx.CartType, storeId); + var storeId = ctx.StoreId ?? _storeContext.CurrentStore.Id; _customerService.ResetCheckoutData(customer, storeId); @@ -1196,7 +1202,7 @@ public virtual void AddToCart(AddToCartContext ctx) } ctx.Warnings.AddRange( - AddToCart(_workContext.CurrentCustomer, ctx.Product, ctx.CartType, storeId, ctx.AttributesXml, ctx.CustomerEnteredPrice, ctx.Quantity, ctx.AddRequiredProducts, ctx) + AddToCart(customer, ctx.Product, ctx.CartType, storeId, ctx.AttributesXml, ctx.CustomerEnteredPrice, ctx.Quantity, ctx.AddRequiredProducts, ctx) ); if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.Warnings.Count <= 0 && ctx.BundleItem == null) diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs index 487d3fb724..65155d6db7 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartTotal.cs @@ -56,7 +56,12 @@ public static implicit operator ShoppingCartTotal(decimal? obj) /// public decimal RedeemedRewardPointsAmount { get; set; } - public ConvertedAmounts ConvertedFromPrimaryStoreCurrency { get; set; } + /// + /// Credit balance. + /// + public decimal CreditBalance { get; set; } + + public ConvertedAmounts ConvertedFromPrimaryStoreCurrency { get; set; } public override string ToString() { diff --git a/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs b/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs new file mode 100644 index 0000000000..1b30b02387 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Payments/CapturePaymentHook.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Services.Orders; + +namespace SmartStore.Services.Payments +{ + public class CapturePaymentHook : DbSaveHook + { + private readonly Lazy _services; + private readonly Lazy _orderProcessingService; + private readonly HashSet _toCapture = new HashSet(); + + public CapturePaymentHook( + Lazy services, + Lazy orderProcessingService) + { + _services = services; + _orderProcessingService = orderProcessingService; + } + + private bool IsStatusPropertyModifiedTo(IHookedEntity entry, string propertyName, int statusId) + { + var prop = entry.Entry.Property(propertyName); + + if (prop != null && prop.CurrentValue != null) + { + if (!prop.CurrentValue.Equals(prop.OriginalValue)) + { + return (int)prop.CurrentValue == statusId; + } + } + + return false; + } + + protected override void OnUpdating(Order entity, IHookedEntity entry) + { + if (entry.State == Core.Data.EntityState.Modified) + { + var isShipped = IsStatusPropertyModifiedTo(entry, nameof(entity.ShippingStatusId), (int)ShippingStatus.Shipped); + var isDelivered = IsStatusPropertyModifiedTo(entry, nameof(entity.ShippingStatusId), (int)ShippingStatus.Delivered); + + if (isShipped || isDelivered) + { + var settings = _services.Value.Settings.LoadSetting(entity.StoreId); + if (settings.CapturePaymentReason.HasValue) + { + if (isShipped && settings.CapturePaymentReason.Value == CapturePaymentReason.OrderShipped) + { + _toCapture.Add(entity); + } + else if (isDelivered && settings.CapturePaymentReason.Value == CapturePaymentReason.OrderDelivered) + { + _toCapture.Add(entity); + } + } + } + + //if (IsStatusPropertyModifiedTo(entry, nameof(entity.OrderStatusId), (int)OrderStatus.Complete)) + //{ + // That's too late. The payment is already marked as paid and the capture process would never be executed. + //} + } + } + + public override void OnAfterSave(IHookedEntity entry) + { + // Do not remove. + } + + public override void OnAfterSaveCompleted() + { + if (_toCapture.Any()) + { + foreach (var order in _toCapture) + { + if (_orderProcessingService.Value.CanCapture(order)) + { + _orderProcessingService.Value.Capture(order); + } + } + + _toCapture.Clear(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 7e5639bba9..4f6298a3b5 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -8,18 +8,17 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Stores; namespace SmartStore.Services.Payments { - /// - /// Payment service - /// - public partial class PaymentService : IPaymentService + /// + /// Payment service + /// + public partial class PaymentService : IPaymentService { - #region Fields - private readonly static object _lock = new object(); private static IList _paymentMethodFilterTypes = null; @@ -32,17 +31,6 @@ public partial class PaymentService : IPaymentService private readonly ICommonServices _services; private readonly ITypeFinder _typeFinder; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Payment settings - /// Plugin finder - /// Shopping cart settings - /// Plugin service public PaymentService( IRepository paymentMethodRepository, IRepository storeMappingRepository, @@ -63,15 +51,15 @@ public PaymentService( _typeFinder = typeFinder; T = NullLocalizer.Instance; - QuerySettings = DbQuerySettings.Default; + Logger = NullLogger.Instance; + QuerySettings = DbQuerySettings.Default; } public Localizer T { get; set; } - public DbQuerySettings QuerySettings { get; set; } + public ILogger Logger { get; set; } + public DbQuerySettings QuerySettings { get; set; } - #endregion - - #region Methods + #region Methods public virtual bool IsPaymentMethodActive(string systemName, int storeId = 0) { @@ -103,9 +91,6 @@ public virtual IEnumerable> LoadActivePaymentMethods( PaymentMethodType[] types = null, bool provideFallbackMethod = true) { - IList allFilters = null; - IEnumerable> allProviders = null; - var filterRequest = new PaymentFilterRequest { Cart = cart, @@ -113,25 +98,34 @@ public virtual IEnumerable> LoadActivePaymentMethods( Customer = customer }; - if (types != null && types.Any()) - allProviders = LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)); - else - allProviders = LoadAllPaymentMethods(storeId); + var allFilters = GetAllPaymentMethodFilters(); + var allProviders = types != null && types.Any() + ? LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)) + : LoadAllPaymentMethods(storeId); var activeProviders = allProviders .Where(p => { - if (!p.Value.IsActive || !_paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)) - return false; - - // payment method filtering - if (allFilters == null) - allFilters = GetAllPaymentMethodFilters(); - - filterRequest.PaymentMethod = p; - - if (allFilters.Any(x => x.IsExcluded(filterRequest))) - return false; + try + { + // Only active payment methods. + if (!p.Value.IsActive || !_paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)) + { + return false; + } + + filterRequest.PaymentMethod = p; + + // Only payment methods that have not been filtered out. + if (allFilters.Any(x => x.IsExcluded(filterRequest))) + { + return false; + } + } + catch (Exception ex) + { + Logger.Error(ex); + } return true; }); @@ -139,22 +133,23 @@ public virtual IEnumerable> LoadActivePaymentMethods( if (!activeProviders.Any() && provideFallbackMethod) { var fallbackMethod = allProviders.FirstOrDefault(x => x.IsPaymentMethodActive(_paymentSettings)); - - if (fallbackMethod == null) - fallbackMethod = allProviders.FirstOrDefault(); + if (fallbackMethod == null) + { + fallbackMethod = allProviders.FirstOrDefault(); + } if (fallbackMethod != null) { return new Provider[] { fallbackMethod }; } - else - { - if (DataSettings.DatabaseIsInstalled()) - throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); - } - } - return activeProviders; + if (DataSettings.DatabaseIsInstalled()) + { + throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); + } + } + + return activeProviders; } public virtual Provider LoadPaymentMethodBySystemName(string systemName, bool onlyWhenActive = false, int storeId = 0) diff --git a/src/Libraries/SmartStore.Services/Pdf/PdfConvertSettings.cs b/src/Libraries/SmartStore.Services/Pdf/PdfConvertSettings.cs index a1f32ace0f..8aa22eb59e 100644 --- a/src/Libraries/SmartStore.Services/Pdf/PdfConvertSettings.cs +++ b/src/Libraries/SmartStore.Services/Pdf/PdfConvertSettings.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Security; namespace SmartStore.Services.Pdf { diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchEvents.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchEvents.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchEvents.cs diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs new file mode 100644 index 0000000000..87fe6940dc --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search +{ + public partial class CatalogSearchResult + { + private readonly int _totalHitsCount; + private readonly Func> _hitsFactory; + private IPagedList _hits; + + public CatalogSearchResult( + ISearchEngine engine, + CatalogSearchQuery query, + int totalHitsCount, + Func> hitsFactory, + string[] spellCheckerSuggestions, + IDictionary facets) + { + Guard.NotNull(query, nameof(query)); + + Engine = engine; + Query = query; + SpellCheckerSuggestions = spellCheckerSuggestions ?? new string[0]; + Facets = facets ?? new Dictionary(); + + _hitsFactory = hitsFactory ?? (() => new List()); + _totalHitsCount = totalHitsCount; + } + + /// + /// Constructor for an instance without any search hits + /// + /// Catalog search query + public CatalogSearchResult(CatalogSearchQuery query) + : this(null, query, 0, () => new List(), null, null) + { + } + + /// + /// Products found + /// + public IPagedList Hits + { + get + { + if (_hits == null) + { + var products = _totalHitsCount == 0 + ? new List() + : _hitsFactory.Invoke(); + + _hits = new PagedList(products, Query.PageIndex, Query.Take, _totalHitsCount); + } + + return _hits; + } + } + + public int TotalHitsCount + { + get { return _totalHitsCount; } + } + + /// + /// The original catalog search query + /// + public CatalogSearchQuery Query + { + get; + private set; + } + + /// + /// Gets spell checking suggestions/corrections + /// + public string[] SpellCheckerSuggestions + { + get; + set; + } + + public IDictionary Facets + { + get; + private set; + } + + public ISearchEngine Engine + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs new file mode 100644 index 0000000000..a1e40b18cc --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchService.cs @@ -0,0 +1,252 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Autofac; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.Search +{ + public partial class CatalogSearchService : SearchServiceBase, ICatalogSearchService + { + private readonly ICommonServices _services; + private readonly IIndexManager _indexManager; + private readonly Lazy _productService; + private readonly ILogger _logger; + private readonly IPriceFormatter _priceFormatter; + private readonly UrlHelper _urlHelper; + + public CatalogSearchService( + ICommonServices services, + IIndexManager indexManager, + Lazy productService, + ILogger logger, + IPriceFormatter priceFormatter, + UrlHelper urlHelper) + { + _services = services; + _indexManager = indexManager; + _productService = productService; + _logger = logger; + _priceFormatter = priceFormatter; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + /// + /// Bypasses the index provider and directly searches in the database + /// + /// Search query + /// LOad flags + /// Catalog search result + protected virtual CatalogSearchResult SearchDirect(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None) + { + // Fallback to linq search. + var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); + + var result = linqCatalogSearchService.Search(searchQuery, loadFlags, true); + ApplyFacetLabels(result.Facets); + + return result; + } + + protected virtual void ApplyFacetLabels(IDictionary facets) + { + if (facets == null || facets.Count == 0) + { + return; + } + + FacetGroup group; + var rangeMinTemplate = T("Search.Facet.RangeMin").Text; + var rangeMaxTemplate = T("Search.Facet.RangeMax").Text; + var rangeBetweenTemplate = T("Search.Facet.RangeBetween").Text; + + // Apply "price" labels. + if (facets.TryGetValue("price", out group)) + { + // TODO: formatting without decimals would be nice + foreach (var facet in group.Facets) + { + var val = facet.Value; + + if (val.Value == null && val.UpperValue != null) + { + val.Label = rangeMaxTemplate.FormatInvariant(FormatPrice(val.UpperValue.Convert())); + } + else if (val.Value != null && val.UpperValue == null) + { + val.Label = rangeMinTemplate.FormatInvariant(FormatPrice(val.Value.Convert())); + } + else if (val.Value != null && val.UpperValue != null) + { + val.Label = rangeBetweenTemplate.FormatInvariant( + FormatPrice(val.Value.Convert()), + FormatPrice(val.UpperValue.Convert())); + } + } + } + + // Apply "rating" labels. + if (facets.TryGetValue("rating", out group)) + { + foreach (var facet in group.Facets) + { + facet.Value.Label = T(facet.Key == "1" ? "Search.Facet.1StarAndMore" : "Search.Facet.XStarsAndMore", facet.Value.Value).Text; + } + } + + // Apply "numeric range" labels. + var numericRanges = facets + .Where(x => x.Value.TemplateHint == FacetTemplateHint.NumericRange) + .Select(x => x.Value); + + foreach (var numericRange in numericRanges) + { + foreach (var facet in numericRange.SelectedFacets) + { + var val = facet.Value; + var labels = val.Label.SplitSafe("~"); + + if (val.Value == null && val.UpperValue != null) + { + val.Label = rangeMaxTemplate.FormatInvariant(labels.SafeGet(0)); + } + else if (val.Value != null && val.UpperValue == null) + { + val.Label = rangeMinTemplate.FormatInvariant(labels.SafeGet(0)); + } + else if (val.Value != null && val.UpperValue != null) + { + val.Label = rangeBetweenTemplate.FormatInvariant(labels.SafeGet(0), labels.SafeGet(1)); + } + } + } + } + + protected virtual string FormatPrice(decimal price) + { + return _priceFormatter.FormatPrice(price, true, false); + } + + public CatalogSearchResult Search( + CatalogSearchQuery searchQuery, + ProductLoadFlags loadFlags = ProductLoadFlags.None, + bool direct = false) + { + Guard.NotNull(searchQuery, nameof(searchQuery)); + Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); + + var provider = _indexManager.GetIndexProvider("Catalog"); + + if (!direct && provider != null) + { + var indexStore = provider.GetIndexStore("Catalog"); + if (indexStore.Exists) + { + var searchEngine = provider.GetSearchEngine(indexStore, searchQuery); + var stepPrefix = searchEngine.GetType().Name + " - "; + + int totalCount = 0; + string[] spellCheckerSuggestions = null; + IEnumerable searchHits; + Func> hitsFactory = null; + IDictionary facets = null; + + _services.EventPublisher.Publish(new CatalogSearchingEvent(searchQuery)); + + if (searchQuery.Take > 0) + { + using (_services.Chronometer.Step(stepPrefix + "Count")) + { + totalCount = searchEngine.Count(); + // Fix paging boundaries + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) + { + searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + } + + using (_services.Chronometer.Step(stepPrefix + "Hits")) + { + searchHits = searchEngine.Search(); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + using (_services.Chronometer.Step(stepPrefix + "Collect")) + { + var productIds = searchHits.Select(x => x.EntityId).ToArray(); + hitsFactory = () => _productService.Value.GetProductsByIds(productIds, loadFlags); + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Facets")) + { + facets = searchEngine.GetFacetMap(); + ApplyFacetLabels(facets); + } + } + catch (Exception ex) + { + _logger.Error(ex); + } + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Spellcheck")) + { + spellCheckerSuggestions = searchEngine.CheckSpelling(); + } + } + catch (Exception ex) + { + // Spell checking should not break the search. + _logger.Error(ex); + } + } + + var result = new CatalogSearchResult( + searchEngine, + searchQuery, + totalCount, + hitsFactory, + spellCheckerSuggestions, + facets); + + _services.EventPublisher.Publish(new CatalogSearchedEvent(searchQuery, result)); + + return result; + } + else if (searchQuery.Origin.IsCaseInsensitiveEqual("Search/Search")) + { + IndexingRequiredNotification(_services, _urlHelper); + } + } + + return SearchDirect(searchQuery); + } + + public IQueryable PrepareQuery(CatalogSearchQuery searchQuery, IQueryable baseQuery = null) + { + var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); + return linqCatalogSearchService.PrepareQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/ICatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/ICatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/ICatalogSearchService.cs diff --git a/src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs similarity index 91% rename from src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index 6ffa7e6827..469fba4e20 100644 --- a/src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; -using SmartStore.Core.Logging; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Catalog; @@ -17,8 +15,8 @@ namespace SmartStore.Services.Search { - public partial class LinqCatalogSearchService : ICatalogSearchService - { + public partial class LinqCatalogSearchService : SearchServiceBase, ICatalogSearchService + { private static int[] _priceThresholds = new int[] { 10, 25, 50, 100, 250, 500, 1000 }; private readonly IProductService _productService; @@ -62,90 +60,11 @@ public LinqCatalogSearchService( _categoryService = categoryService; QuerySettings = DbQuerySettings.Default; - Logger = NullLogger.Instance; } public DbQuerySettings QuerySettings { get; set; } - public ILogger Logger { get; set; } - - #region Utilities - - private void FlattenFilters(ICollection filters, List result) - { - foreach (var filter in filters) - { - var combinedFilter = filter as ICombinedSearchFilter; - if (combinedFilter != null) - { - FlattenFilters(combinedFilter.Filters, result); - } - else - { - result.Add(filter); - } - } - } - - private ISearchFilter FindFilter(ICollection filters, string fieldName) - { - if (fieldName.HasValue()) - { - foreach (var filter in filters) - { - var attributeFilter = filter as IAttributeSearchFilter; - if (attributeFilter != null && attributeFilter.FieldName == fieldName) - { - return attributeFilter; - } - - var combinedFilter = filter as ICombinedSearchFilter; - if (combinedFilter != null) - { - var filter2 = FindFilter(combinedFilter.Filters, fieldName); - if (filter2 != null) - { - return filter2; - } - } - } - } - - return null; - } - - private List GetIdList(List filters, string fieldName) - { - var result = new List(); - - foreach (IAttributeSearchFilter filter in filters) - { - if (!(filter is IRangeSearchFilter) && filter.FieldName == fieldName) - result.Add((int)filter.Term); - } - - return result; - } - - private IOrderedQueryable OrderBy(ref bool ordered, IQueryable query, Expression> keySelector, bool descending = false) - { - if (ordered) - { - if (descending) - return ((IOrderedQueryable)query).ThenByDescending(keySelector); - - return ((IOrderedQueryable)query).ThenBy(keySelector); - } - else - { - ordered = true; - - if (descending) - return query.OrderByDescending(keySelector); - - return query.OrderBy(keySelector); - } - } + #region Utilities private IQueryable QueryCategories(IQueryable query, List ids, bool? featuredOnly) { @@ -223,7 +142,7 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ var utcNow = DateTime.UtcNow; var query = baseQuery ?? _productRepository.Table; - query = query.Where(x => !x.Deleted); + query = query.Where(x => !x.Deleted && !x.IsSystemProduct); query = ApplySearchTerm(query, searchQuery); #region Filters @@ -615,7 +534,7 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s { var descriptor = searchQuery.FacetDescriptors[key]; var facets = new List(); - var kind = FacetGroup.GetKindByKey(key); + var kind = FacetGroup.GetKindByKey("Catalog", key); switch (kind) { @@ -633,8 +552,6 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s if (kind == FacetGroupKind.Category) { - #region Category - var categoryTree = _categoryService.GetCategoryTree(0, false, storeId); var categories = categoryTree.Flatten(false); @@ -659,13 +576,9 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = category.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.Brand) { - #region Brand - var manufacturers = _manufacturerService.GetAllManufacturers(null, storeId); if (descriptor.MaxChoicesCount > 0) { @@ -688,13 +601,9 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = manu.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.DeliveryTime) { - #region Delivery time - var deliveryTimes = _deliveryTimeService.GetAllDeliveryTimes(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "DeliveryTime" && x.LocaleKey == "Name" && x.LanguageId == languageId); @@ -715,17 +624,13 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s DisplayOrder = deliveryTime.DisplayOrder })); } - - #endregion } else if (kind == FacetGroupKind.Price) { - #region Price - var count = 0; var hasActivePredefinedFacet = false; - var minPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published).Min(x => (double)x.Price); - var maxPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published).Max(x => (double)x.Price); + var minPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published && !x.IsSystemProduct).Min(x => (double)x.Price); + var maxPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published && !x.IsSystemProduct).Max(x => (double)x.Price); minPrice = FacetUtility.MakePriceEven(minPrice); maxPrice = FacetUtility.MakePriceEven(maxPrice); @@ -768,8 +673,6 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } - - #endregion } else if (kind == FacetGroupKind.Rating) { @@ -805,7 +708,8 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); result.Add(key, new FacetGroup( - key, + "Catalog", + key, descriptor.Label, descriptor.IsMultiSelect, false, @@ -817,7 +721,7 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s return result; } - #endregion + #endregion public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false) { diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs similarity index 97% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs index 42562c44c6..67c3ad8337 100644 --- a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -10,10 +10,11 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; namespace SmartStore.Services.Search.Modelling { - public class CatalogSearchQueryAliasMapper : ICatalogSearchQueryAliasMapper + public class CatalogSearchQueryAliasMapper : ICatalogSearchQueryAliasMapper { private const string ALL_ATTRIBUTE_ID_BY_ALIAS_KEY = "search.attribute.id.alias.mappings.all"; private const string ALL_ATTRIBUTE_ALIAS_BY_ID_KEY = "search.attribute.alias.id.mappings.all"; @@ -66,11 +67,6 @@ protected string CreateOptionKey(string prefix, int languageId, int optionId) return $"{prefix}.{languageId}.{optionId}"; } - protected string CreateSettingKey(FacetGroupKind kind, int languageId) - { - return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; - } - protected void CachedLocalizedAlias(string localeKeyGroup, Action caching) { _localizedPropertyRepository.TableUntracked @@ -297,8 +293,7 @@ protected virtual IDictionary GetVariantIdByAliasMappings() options.Clear(); var optionIdMappings = _productVariantAttributeValueRepository.TableUntracked - .Expand(x => x.ProductVariantAttribute) - .Expand("ProductVariantAttribute.ProductAttribute") + .Expand(x => x.ProductVariantAttribute.ProductAttribute) .Select(x => new { OptionId = x.Id, @@ -465,7 +460,7 @@ protected virtual IDictionary GetCommonFacetAliasByGroupKindMapp { foreach (var groupKind in groupKinds) { - var key = CreateSettingKey(groupKind, language.Id); + var key = FacetUtility.GetFacetAliasSettingKey(groupKind, language.Id); var value = _settingService.GetSettingByKey(key); if (value.HasValue()) { @@ -487,7 +482,7 @@ public string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId { var mappings = GetCommonFacetAliasByGroupKindMappings(); - return mappings.Get(CreateSettingKey(kind, languageId)); + return mappings.Get(FacetUtility.GetFacetAliasSettingKey(kind, languageId)); } #endregion diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs similarity index 85% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs index 808fd62679..4f4674d168 100644 --- a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs @@ -3,17 +3,17 @@ using System.Linq; using System.Web; using System.Web.Routing; -using SmartStore.Collections; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; using SmartStore.Services.Catalog; -using SmartStore.Utilities; +using SmartStore.Services.Search.Extensions; +using SmartStore.Services.Security; namespace SmartStore.Services.Search.Modelling { - /* + /* TOKENS: =============================== q - Search term @@ -32,17 +32,14 @@ namespace SmartStore.Services.Search.Modelling * - Variants & attributes */ - public class CatalogSearchQueryFactory : ICatalogSearchQueryFactory - { - protected static readonly string[] _tokens = new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; + public class CatalogSearchQueryFactory : SearchQueryFactoryBase, ICatalogSearchQueryFactory + { protected static readonly string[] _instantSearchFields = new string[] { "manufacturer", "sku", "gtin", "mpn", "attrname", "variantname" }; - protected readonly HttpContextBase _httpContext; protected readonly CatalogSettings _catalogSettings; protected readonly SearchSettings _searchSettings; protected readonly ICommonServices _services; protected readonly ICatalogSearchQueryAliasMapper _catalogSearchQueryAliasMapper; - private Multimap _aliases; public CatalogSearchQueryFactory( HttpContextBase httpContext, @@ -50,8 +47,8 @@ public CatalogSearchQueryFactory( SearchSettings searchSettings, ICommonServices services, ICatalogSearchQueryAliasMapper catalogSearchQueryAliasMapper) + : base(httpContext) { - _httpContext = httpContext; _catalogSettings = catalogSettings; _searchSettings = searchSettings; _services = services; @@ -64,7 +61,9 @@ public CatalogSearchQueryFactory( public CatalogSearchQuery Current { get; private set; } - public CatalogSearchQuery CreateFromQuery() + protected override string[] Tokens => new string[] { "q", "i", "s", "o", "p", "c", "m", "r", "a", "n", "d", "v" }; + + public CatalogSearchQuery CreateFromQuery() { var ctx = _httpContext; @@ -88,7 +87,9 @@ public CatalogSearchQuery CreateFromQuery() foreach (var fieldName in _instantSearchFields) { if (_searchSettings.SearchFields.Contains(fieldName)) + { fields.Add(fieldName); + } } } else @@ -103,19 +104,19 @@ public CatalogSearchQuery CreateFromQuery() .VisibleIndividuallyOnly(true) .BuildFacetMap(!isInstantSearch); - // Visibility + // Visibility. query.VisibleOnly(!QuerySettings.IgnoreAcl ? _services.WorkContext.CurrentCustomer : null); - // Store + // Store. if (!QuerySettings.IgnoreMultiStore) { query.HasStoreId(_services.StoreContext.CurrentStore.Id); } - // Availability + // Availability. ConvertAvailability(query, routeData, origin); - // Instant-Search never uses these filter parameters + // Instant-Search never uses these filter parameters. if (!isInstantSearch) { ConvertPagingSorting(query, routeData, origin); @@ -129,8 +130,7 @@ public CatalogSearchQuery CreateFromQuery() OnConverted(query, routeData, origin); - this.Current = query; - + Current = query; return query; } @@ -144,14 +144,7 @@ protected virtual void ConvertPagingSorting(CatalogSearchQuery query, RouteData var orderBy = GetValueFor("o"); if (orderBy == null || orderBy == ProductSortingEnum.Initial) { - if(origin.Equals("Search/Search")) - { - orderBy = _searchSettings.DefaultSortOrder; - } - else - { - orderBy = _catalogSettings.DefaultSortOrder; - } + orderBy = origin.IsCaseInsensitiveEqual("Search/Search") ? _searchSettings.DefaultSortOrder : _catalogSettings.DefaultSortOrder; } query.CustomData["CurrentSortOrder"] = orderBy.Value; @@ -304,7 +297,7 @@ private void AddFacet( displayOrder = _searchSettings.BrandDisplayOrder; break; case FacetGroupKind.Price: - if (_searchSettings.PriceDisabled) + if (_searchSettings.PriceDisabled || !_services.Permissions.Authorize(StandardPermissionProvider.DisplayPrices)) return; fieldName = "price"; displayOrder = _searchSettings.PriceDisplayOrder; @@ -338,7 +331,7 @@ private void AddFacet( } var descriptor = new FacetDescriptor(fieldName); - descriptor.Label = _services.Localization.GetResource(FacetDescriptor.GetLabelResourceKey(kind) ?? kind.ToString()); + descriptor.Label = _services.Localization.GetResource(FacetUtility.GetLabelResourceKey(kind) ?? kind.ToString()); descriptor.IsMultiSelect = isMultiSelect; descriptor.DisplayOrder = displayOrder; descriptor.OrderBy = sorting; @@ -481,27 +474,6 @@ protected virtual void ConvertPrice(CatalogSearchQuery query, RouteData routeDat }); } - protected virtual bool TryParseRange(string query, out double? min, out double? max) - { - min = max = null; - - if (query.IsEmpty()) - { - return false; - } - - // Format: from~to || from[~] || ~to - var arr = query.Split('~').Select(x => x.Trim()).Take(2).ToArray(); - - CommonHelper.TryConvert(arr[0], out min); - if (arr.Length == 2) - { - CommonHelper.TryConvert(arr[1], out max); - } - - return min != null || max != null; - } - protected virtual void ConvertRating(CatalogSearchQuery query, RouteData routeData, string origin) { double? fromRate; @@ -619,70 +591,9 @@ protected virtual void OnConverted(CatalogSearchQuery query, RouteData routeData { } - protected T GetValueFor(string key) - { - T value; - return GetValueFor(key, out value) ? value : default(T); - } - - protected bool GetValueFor(string key, out T value) - { - var strValue = _httpContext.Request?.Unvalidated.Form?[key] ?? _httpContext.Request?.Unvalidated.QueryString?[key]; - - if (strValue != null) - { - value = strValue.Convert(); - return true; - } - - value = default(T); - return false; - } - - protected bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, out T value) + protected virtual bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, out T value) { return GetValueFor(_catalogSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); } - - protected Multimap Aliases - { - get - { - if (_aliases == null) - { - _aliases = new Multimap(); - - if (_httpContext.Request != null) - { - var form = _httpContext.Request.Form; - var query = _httpContext.Request.QueryString; - - if (form != null) - { - foreach (var key in form.AllKeys) - { - if (key.HasValue() && !_tokens.Contains(key)) - { - _aliases.AddRange(key, form[key].SplitSafe(",")); - } - } - } - - if (query != null) - { - foreach (var key in query.AllKeys) - { - if (key.HasValue() && !_tokens.Contains(key)) - { - _aliases.AddRange(key, query[key].SplitSafe(",")); - } - } - } - } - } - - return _aliases; - } - } } } diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs similarity index 88% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryModelBinder.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs index e92cf89e61..dce6a49f57 100644 --- a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryModelBinder.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryModelBinder.cs @@ -1,10 +1,9 @@ -using System; -using System.Web.Mvc; +using System.Web.Mvc; using Autofac.Integration.Mvc; namespace SmartStore.Services.Search.Modelling { - [ModelBinderType(typeof(CatalogSearchQuery))] + [ModelBinderType(typeof(CatalogSearchQuery))] public class CatalogSearchQueryModelBinder : IModelBinder { private readonly ICatalogSearchQueryFactory _factory; @@ -18,7 +17,7 @@ public object BindModel(ControllerContext controllerContext, ModelBindingContext { if (_factory.Current != null) { - // Dont' bind again for current request + // Don't bind again for current request. return _factory.Current; } @@ -31,16 +30,13 @@ public object BindModel(ControllerContext controllerContext, ModelBindingContext } var modelType = bindingContext.ModelType; - if (modelType != typeof(CatalogSearchQuery)) { return new CatalogSearchQuery(); } var query = _factory.CreateFromQuery(); - return query; - } } } diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryAliasMapper.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryAliasMapper.cs diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs similarity index 100% rename from src/Libraries/SmartStore.Services/Search/Modelling/ICatalogSearchQueryFactory.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/ICatalogSearchQueryFactory.cs diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchResult.cs b/src/Libraries/SmartStore.Services/Search/CatalogSearchResult.cs deleted file mode 100644 index e37c1f3c7f..0000000000 --- a/src/Libraries/SmartStore.Services/Search/CatalogSearchResult.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Search; -using SmartStore.Core.Search.Facets; - -namespace SmartStore.Services.Search -{ - public partial class CatalogSearchResult - { - private readonly int _totalHitsCount; - private readonly Func> _hitsFactory; - private IPagedList _hits; - - public CatalogSearchResult( - ISearchEngine engine, - CatalogSearchQuery query, - int totalHitsCount, - Func> hitsFactory, - string[] spellCheckerSuggestions, - IDictionary facets) - { - Guard.NotNull(query, nameof(query)); - - Engine = engine; - Query = query; - SpellCheckerSuggestions = spellCheckerSuggestions ?? new string[0]; - Facets = facets ?? new Dictionary(); - - _hitsFactory = hitsFactory ?? (() => new List()); - _totalHitsCount = totalHitsCount; - } - - /// - /// Constructor for an instance without any search hits - /// - /// Catalog search query - public CatalogSearchResult(CatalogSearchQuery query) - : this(null, query, 0, () => new List(), null, null) - { - } - - /// - /// Products found - /// - public IPagedList Hits - { - get - { - if (_hits == null) - { - var products = _totalHitsCount == 0 - ? new List() - : _hitsFactory.Invoke(); - - _hits = new PagedList(products, Query.PageIndex, Query.Take, _totalHitsCount); - } - - return _hits; - } - } - - public int TotalHitsCount - { - get { return _totalHitsCount; } - } - - /// - /// The original catalog search query - /// - public CatalogSearchQuery Query - { - get; - private set; - } - - /// - /// Gets spell checking suggestions/corrections - /// - public string[] SpellCheckerSuggestions - { - get; - set; - } - - public IDictionary Facets - { - get; - private set; - } - - public ISearchEngine Engine - { - get; - private set; - } - - /// - /// Highlights chosen terms in a text, extracting the most relevant sections - /// - /// Text to highlight terms in - /// Highlighted text fragments - public string Highlight(string input, string preMatch = "", string postMatch = "", bool useSearchEngine = true) - { - if (Query?.Term == null || input.IsEmpty()) - return input; - - string hilite = null; - - if (useSearchEngine && Engine != null) - { - try - { - hilite = Engine.Highlight(input, preMatch, postMatch); - } - catch { } - } - - if (hilite.HasValue()) - { - return hilite; - } - - return input.HighlightKeywords(Query.Term, preMatch, postMatch); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs b/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs deleted file mode 100644 index 5f9b9bfa79..0000000000 --- a/src/Libraries/SmartStore.Services/Search/CatalogSearchService.cs +++ /dev/null @@ -1,268 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Mvc; -using Autofac; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Localization; -using SmartStore.Core.Logging; -using SmartStore.Core.Search; -using SmartStore.Core.Search.Facets; -using SmartStore.Services.Catalog; -using SmartStore.Services.Customers; - -namespace SmartStore.Services.Search -{ - public partial class CatalogSearchService : ICatalogSearchService - { - private readonly ICommonServices _services; - private readonly IIndexManager _indexManager; - private readonly Lazy _productService; - private readonly ILogger _logger; - private readonly IPriceFormatter _priceFormatter; - private readonly UrlHelper _urlHelper; - - public CatalogSearchService( - ICommonServices services, - IIndexManager indexManager, - Lazy productService, - ILogger logger, - IPriceFormatter priceFormatter, - UrlHelper urlHelper) - { - _services = services; - _indexManager = indexManager; - _productService = productService; - _logger = logger; - _priceFormatter = priceFormatter; - _urlHelper = urlHelper; - - T = NullLocalizer.Instance; - } - - public Localizer T { get; set; } - - /// - /// Notifies admin that indexing is required to use the advanced search. - /// - protected virtual void IndexingRequiredNotification() - { - if (_services.WorkContext.CurrentCustomer.IsAdmin()) - { - var notification = T("Search.IndexingRequiredNotification", - _urlHelper.Action("Indexing", "MegaSearch", new { area = "SmartStore.MegaSearch" }), - _urlHelper.Action("ConfigurePlugin", "Plugin", new { area = "admin", systemName = "SmartStore.MegaSearch" })); - - _services.Notifier.Information(notification); - } - } - - /// - /// Bypasses the index provider and directly searches in the database - /// - /// - /// - /// - protected virtual CatalogSearchResult SearchDirect(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None) - { - // fallback to linq search - var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); - - var result = linqCatalogSearchService.Search(searchQuery, loadFlags, true); - ApplyFacetLabels(result.Facets); - - return result; - } - - public CatalogSearchResult Search( - CatalogSearchQuery searchQuery, - ProductLoadFlags loadFlags = ProductLoadFlags.None, - bool direct = false) - { - Guard.NotNull(searchQuery, nameof(searchQuery)); - Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); - - var provider = _indexManager.GetIndexProvider(); - - if (!direct && provider != null) - { - var indexStore = provider.GetIndexStore("Catalog"); - if (indexStore.Exists) - { - var searchEngine = provider.GetSearchEngine(indexStore, searchQuery); - var stepPrefix = searchEngine.GetType().Name + " - "; - - int totalCount = 0; - string[] spellCheckerSuggestions = null; - IEnumerable searchHits; - Func> hitsFactory = null; - IDictionary facets = null; - - _services.EventPublisher.Publish(new CatalogSearchingEvent(searchQuery)); - - if (searchQuery.Take > 0) - { - using (_services.Chronometer.Step(stepPrefix + "Count")) - { - totalCount = searchEngine.Count(); - // Fix paging boundaries - if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) - { - searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); - } - } - - using (_services.Chronometer.Step(stepPrefix + "Hits")) - { - searchHits = searchEngine.Search(); - } - - if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) - { - using (_services.Chronometer.Step(stepPrefix + "Collect")) - { - var productIds = searchHits.Select(x => x.EntityId).ToArray(); - hitsFactory = () => _productService.Value.GetProductsByIds(productIds, loadFlags); - } - } - - if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets)) - { - try - { - using (_services.Chronometer.Step(stepPrefix + "Facets")) - { - facets = searchEngine.GetFacetMap(); - ApplyFacetLabels(facets); - } - } - catch (Exception exception) - { - _logger.Error(exception); - } - } - } - - if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) - { - try - { - using (_services.Chronometer.Step(stepPrefix + "Spellcheck")) - { - spellCheckerSuggestions = searchEngine.CheckSpelling(); - } - } - catch (Exception exception) - { - // spell checking should not break the search - _logger.Error(exception); - } - } - - var result = new CatalogSearchResult( - searchEngine, - searchQuery, - totalCount, - hitsFactory, - spellCheckerSuggestions, - facets); - - _services.EventPublisher.Publish(new CatalogSearchedEvent(searchQuery, result)); - - return result; - } - else if (searchQuery.Origin.IsCaseInsensitiveEqual("Search/Search")) - { - IndexingRequiredNotification(); - } - } - - return SearchDirect(searchQuery); - } - - public IQueryable PrepareQuery(CatalogSearchQuery searchQuery, IQueryable baseQuery = null) - { - var linqCatalogSearchService = _services.Container.ResolveNamed("linq"); - return linqCatalogSearchService.PrepareQuery(searchQuery, baseQuery); - } - - protected virtual void ApplyFacetLabels(IDictionary facets) - { - if (facets == null || facets.Count == 0) - { - return; - } - - FacetGroup group; - var rangeMinTemplate = T("Search.Facet.RangeMin").Text; - var rangeMaxTemplate = T("Search.Facet.RangeMax").Text; - var rangeBetweenTemplate = T("Search.Facet.RangeBetween").Text; - - // Apply "price" labels. - if (facets.TryGetValue("price", out group)) - { - // TODO: formatting without decimals would be nice - foreach (var facet in group.Facets) - { - var val = facet.Value; - - if (val.Value == null && val.UpperValue != null) - { - val.Label = rangeMaxTemplate.FormatInvariant(FormatPrice(val.UpperValue.Convert())); - } - else if (val.Value != null && val.UpperValue == null) - { - val.Label = rangeMinTemplate.FormatInvariant(FormatPrice(val.Value.Convert())); - } - else if (val.Value != null && val.UpperValue != null) - { - val.Label = rangeBetweenTemplate.FormatInvariant( - FormatPrice(val.Value.Convert()), - FormatPrice(val.UpperValue.Convert())); - } - } - } - - // Apply "rating" labels. - if (facets.TryGetValue("rating", out group)) - { - foreach (var facet in group.Facets) - { - facet.Value.Label = T(facet.Key == "1" ? "Search.Facet.1StarAndMore" : "Search.Facet.XStarsAndMore", facet.Value.Value).Text; - } - } - - // Apply "numeric range" labels. - var numericRanges = facets - .Where(x => x.Value.TemplateHint == FacetTemplateHint.NumericRange) - .Select(x => x.Value); - - foreach (var numericRange in numericRanges) - { - foreach (var facet in numericRange.SelectedFacets) - { - var val = facet.Value; - var labels = val.Label.SplitSafe("~"); - - if (val.Value == null && val.UpperValue != null) - { - val.Label = rangeMaxTemplate.FormatInvariant(labels.SafeGet(0)); - } - else if (val.Value != null && val.UpperValue == null) - { - val.Label = rangeMinTemplate.FormatInvariant(labels.SafeGet(0)); - } - else if (val.Value != null && val.UpperValue != null) - { - val.Label = rangeBetweenTemplate.FormatInvariant(labels.SafeGet(0), labels.SafeGet(1)); - } - } - } - } - - private string FormatPrice(decimal price) - { - return _priceFormatter.FormatPrice(price, true, false); - } - } -} diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs index 19381fe342..d39648af11 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUrlHelper.cs @@ -12,12 +12,12 @@ namespace SmartStore.Services.Search.Extensions { - public class FacetUrlHelper + public class FacetUrlHelper { - private readonly ICatalogSearchQueryAliasMapper _mapper; - private readonly IWorkContext _workContext; + private readonly ICatalogSearchQueryAliasMapper _catalogAliasMapper; + private readonly IForumSearchQueryAliasMapper _forumAliasMapper; + private readonly IWorkContext _workContext; private readonly HttpRequestBase _httpRequest; - private readonly SearchSettings _searchSettings; private readonly int _languageId; private readonly string _url; @@ -25,25 +25,31 @@ public class FacetUrlHelper private readonly static IDictionary _queryNames = new Dictionary { + // Catalog. { FacetGroupKind.Brand, "m" }, { FacetGroupKind.Category, "c" }, { FacetGroupKind.Price, "p" }, { FacetGroupKind.Rating, "r" }, { FacetGroupKind.DeliveryTime, "d" }, { FacetGroupKind.Availability, "a" }, - { FacetGroupKind.NewArrivals, "n" } - }; + { FacetGroupKind.NewArrivals, "n" }, + + // Forum. + { FacetGroupKind.Forum, "f" }, + { FacetGroupKind.Customer, "c" }, + { FacetGroupKind.Date, "d" } + }; public FacetUrlHelper( - ICatalogSearchQueryAliasMapper mapper, - IWorkContext workContext, - HttpRequestBase httpRequest, - SearchSettings searchSettings) + ICatalogSearchQueryAliasMapper catalogAliasMapper, + IForumSearchQueryAliasMapper forumAliasMapper, + IWorkContext workContext, + HttpRequestBase httpRequest) { - _mapper = mapper; + _catalogAliasMapper = catalogAliasMapper; + _forumAliasMapper = forumAliasMapper; _workContext = workContext; _httpRequest = httpRequest; - _searchSettings = searchSettings; _languageId = _workContext.WorkingLanguage.Id; _url = _httpRequest.CurrentExecutionFilePath; @@ -111,6 +117,9 @@ public string Remove(params Facet[] facets) case FacetGroupKind.DeliveryTime: case FacetGroupKind.Availability: case FacetGroupKind.NewArrivals: + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: + case FacetGroupKind.Date: qsName = _queryNames[facet.FacetGroup.Kind]; break; } @@ -173,15 +182,15 @@ protected virtual NameValueCollection GetQueryParts(Facet facet) else { entityId = val.Value.Convert(); - value = _mapper.GetAttributeOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + value = _catalogAliasMapper.GetAttributeOptionAliasById(entityId, _languageId) ?? "opt" + entityId; } - name = _mapper.GetAttributeAliasById(val.ParentId, _languageId) ?? "attr" + val.ParentId; + name = _catalogAliasMapper.GetAttributeAliasById(val.ParentId, _languageId) ?? "attr" + val.ParentId; result.Add(name, value); break; case FacetGroupKind.Variant: entityId = val.Value.Convert(); - name = _mapper.GetVariantAliasById(val.ParentId, _languageId) ?? "vari" + val.ParentId; - value = _mapper.GetVariantOptionAliasById(entityId, _languageId) ?? "opt" + entityId; + name = _catalogAliasMapper.GetVariantAliasById(val.ParentId, _languageId) ?? "vari" + val.ParentId; + value = _catalogAliasMapper.GetVariantOptionAliasById(entityId, _languageId) ?? "opt" + entityId; result.Add(name, value); break; case FacetGroupKind.Category: @@ -194,11 +203,20 @@ protected virtual NameValueCollection GetQueryParts(Facet facet) value = val.ToString(); if (value.HasValue()) { - name = _mapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; + name = _catalogAliasMapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; result.Add(name, value); } - break; + case FacetGroupKind.Forum: + case FacetGroupKind.Customer: + case FacetGroupKind.Date: + value = val.ToString(); + if (value.HasValue()) + { + name = _forumAliasMapper.GetCommonFacetAliasByGroupKind(group.Kind, _languageId) ?? _queryNames[group.Kind]; + result.Add(name, value); + } + break; } return result; diff --git a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs index 4c799338b7..26bb723fba 100644 --- a/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs +++ b/src/Libraries/SmartStore.Services/Search/Extensions/FacetUtility.cs @@ -1,12 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Search; using SmartStore.Core.Search.Facets; namespace SmartStore.Services.Search.Extensions { - public static class FacetUtility + /// + /// Contains methods that are specifically required for facet processing. + /// + public static class FacetUtility { private const double MAX_PRICE = 1000000000; @@ -33,6 +40,18 @@ public static class FacetUtility { 50000000, 5000000 } }; + private static string GetPublicName(string firstName, string lastName) + { + string result = firstName; + + if (!string.IsNullOrWhiteSpace(result) && !string.IsNullOrWhiteSpace(lastName)) + { + result = string.Concat(result, " ", lastName.First(), "."); + } + + return result; + } + public static double GetNextPrice(double price) { for (var i = 0; i <= _priceThresholds.GetUpperBound(0); ++i) @@ -120,5 +139,92 @@ public static IEnumerable GetRatings() }; } } - } + + public static IQueryable GetCustomersByNumberOfPosts( + IRepository forumPostRepository, + IRepository storeMappingRepository, + int storeId, + int minHitCount = 1) + { + var postQuery = forumPostRepository.TableUntracked + .Expand(x => x.Customer) + .Expand(x => x.Customer.BillingAddress) + .Expand(x => x.Customer.ShippingAddress) + .Expand(x => x.Customer.Addresses); + + if (storeId > 0) + { + postQuery = + from p in postQuery + join sm in storeMappingRepository.TableUntracked on new { eid = p.ForumTopic.Forum.ForumGroupId, ename = "ForumGroup" } equals new { eid = sm.EntityId, ename = sm.EntityName } into gsm + from sm in gsm.DefaultIfEmpty() + where !p.ForumTopic.Forum.ForumGroup.LimitedToStores || sm.StoreId == storeId + select p; + } + + var groupQuery = + from p in postQuery + group p by p.CustomerId into grp + select new + { + Count = grp.Count(), + grp.FirstOrDefault().Customer // Cannot be null. + }; + + groupQuery = minHitCount > 1 + ? groupQuery.Where(x => x.Count >= minHitCount) + : groupQuery; + + var query = groupQuery + .OrderByDescending(x => x.Count) + .Select(x => x.Customer) + .Where(x => x.CustomerRoles.FirstOrDefault(y => y.SystemName == SystemCustomerRoleNames.Guests) == null && !x.Deleted && x.Active && !x.IsSystemAccount); + + return query; + } + + /// + /// Gets the string resource key for a facet group kind + /// + /// Facet group kind + /// Resource key + public static string GetLabelResourceKey(FacetGroupKind kind) + { + switch (kind) + { + case FacetGroupKind.Category: + return "Search.Facet.Category"; + case FacetGroupKind.Brand: + return "Search.Facet.Manufacturer"; + case FacetGroupKind.Price: + return "Search.Facet.Price"; + case FacetGroupKind.Rating: + return "Search.Facet.Rating"; + case FacetGroupKind.DeliveryTime: + return "Search.Facet.DeliveryTime"; + case FacetGroupKind.Availability: + return "Search.Facet.Availability"; + case FacetGroupKind.NewArrivals: + return "Search.Facet.NewArrivals"; + case FacetGroupKind.Forum: + return "Search.Facet.Forum"; + case FacetGroupKind.Customer: + return "Search.Facet.Customer"; + case FacetGroupKind.Date: + return "Search.Facet.Date"; + default: + return null; + } + } + + public static string GetFacetAliasSettingKey(FacetGroupKind kind, int languageId, string scope = null) + { + if (scope.HasValue()) + { + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}-{scope}"; + } + + return $"FacetGroupKind-{kind.ToString()}-Alias-{languageId}"; + } + } } diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs new file mode 100644 index 0000000000..7c1c6f986c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchEvents.cs @@ -0,0 +1,29 @@ +namespace SmartStore.Services.Search +{ + public class ForumSearchingEvent + { + public ForumSearchingEvent(ForumSearchQuery query) + { + Guard.NotNull(query, nameof(query)); + + Query = query; + } + + public ForumSearchQuery Query { get; private set; } + } + + public class ForumSearchedEvent + { + public ForumSearchedEvent(ForumSearchQuery query, ForumSearchResult result) + { + Guard.NotNull(query, nameof(query)); + Guard.NotNull(result, nameof(result)); + + Query = query; + Result = result; + } + + public ForumSearchQuery Query { get; private set; } + public ForumSearchResult Result { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs new file mode 100644 index 0000000000..a6b761cec1 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -0,0 +1,119 @@ +using System; +using System.Linq; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Search; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchQuery : SearchQuery, ICloneable + { + /// + /// Initializes a new instance of the class without a search term being set. + /// + public ForumSearchQuery() + : base((string[])null, null) + { + } + + public ForumSearchQuery(string field, string term, SearchMode mode = SearchMode.Contains, bool escape = true, bool isFuzzySearch = false) + : base(field.HasValue() ? new[] { field } : null, term, mode, escape, isFuzzySearch) + { + } + + public ForumSearchQuery(string[] fields, string term, SearchMode mode = SearchMode.Contains, bool escape = true, bool isFuzzySearch = false) + : base(fields, term, mode, escape, isFuzzySearch) + { + } + + public ForumSearchQuery Clone() + { + return (ForumSearchQuery)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + + #region Fluent builder + + public ForumSearchQuery SortBy(ForumTopicSorting sort) + { + switch (sort) + { + case ForumTopicSorting.SubjectAsc: + case ForumTopicSorting.SubjectDesc: + return SortBy(SearchSort.ByStringField("subject", sort == ForumTopicSorting.SubjectDesc)); + + case ForumTopicSorting.UserNameAsc: + case ForumTopicSorting.UserNameDesc: + return SortBy(SearchSort.ByStringField("username", sort == ForumTopicSorting.UserNameDesc)); + + case ForumTopicSorting.CreatedOnAsc: + case ForumTopicSorting.CreatedOnDesc: + return SortBy(SearchSort.ByDateTimeField("createdon", sort == ForumTopicSorting.CreatedOnDesc)); + + case ForumTopicSorting.PostsAsc: + case ForumTopicSorting.PostsDesc: + return SortBy(SearchSort.ByIntField("numposts", sort == ForumTopicSorting.PostsDesc)); + + case ForumTopicSorting.Relevance: + return SortBy(SearchSort.ByRelevance()); + + default: + return this; + } + } + + public override ForumSearchQuery HasStoreId(int id) + { + base.HasStoreId(id); + + if (id == 0) + { + WithFilter(SearchFilter.ByField("storeid", 0).ExactMatch().NotAnalyzed()); + } + else + { + WithFilter(SearchFilter.Combined( + SearchFilter.ByField("storeid", 0).ExactMatch().NotAnalyzed(), + SearchFilter.ByField("storeid", id).ExactMatch().NotAnalyzed()) + ); + } + + return this; + } + + public ForumSearchQuery WithForumIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("forumid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public ForumSearchQuery WithCustomerIds(params int[] ids) + { + if (ids.Length == 0) + { + return this; + } + + return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField("customerid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + + public ForumSearchQuery CreatedBetween(DateTime? fromUtc, DateTime? toUtc) + { + if (fromUtc == null && toUtc == null) + { + return this; + } + + return WithFilter(SearchFilter.ByRange("createdon", fromUtc, toUtc, fromUtc.HasValue, toUtc.HasValue).Mandatory().ExactMatch().NotAnalyzed()); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs new file mode 100644 index 0000000000..627fb954a1 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchResult.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchResult + { + private readonly Func> _hitsFactory; + private IPagedList _hits; + + public ForumSearchResult( + ISearchEngine engine, + ForumSearchQuery query, + int totalHitsCount, + Func> hitsFactory, + string[] spellCheckerSuggestions, + IDictionary facets) + { + Guard.NotNull(query, nameof(query)); + + Engine = engine; + Query = query; + SpellCheckerSuggestions = spellCheckerSuggestions ?? new string[0]; + Facets = facets ?? new Dictionary(); + + _hitsFactory = hitsFactory ?? (() => new List()); + TotalHitsCount = totalHitsCount; + } + + /// + /// Constructor for an instance without any search hits + /// + /// Forum search query + public ForumSearchResult(ForumSearchQuery query) + : this(null, query, 0, () => new List(), null, null) + { + } + + /// + /// Forum posts found. + /// + public IPagedList Hits + { + get + { + if (_hits == null) + { + var entities = TotalHitsCount == 0 + ? new List() + : _hitsFactory.Invoke(); + + _hits = new PagedList(entities, Query.PageIndex, Query.Take, TotalHitsCount); + } + + return _hits; + } + } + + public int TotalHitsCount { get; } + + /// + /// The original forum search query. + /// + public ForumSearchQuery Query { get; private set; } + + /// + /// Gets spell checking suggestions/corrections. + /// + public string[] SpellCheckerSuggestions { get; set; } + + public IDictionary Facets { get; private set; } + + public ISearchEngine Engine { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs new file mode 100644 index 0000000000..1622b6008f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchService.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using Autofac; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Logging; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Forums; + +namespace SmartStore.Services.Search +{ + public partial class ForumSearchService : SearchServiceBase, IForumSearchService + { + private readonly ICommonServices _services; + private readonly IIndexManager _indexManager; + private readonly Lazy _forumService; + private readonly ILogger _logger; + private readonly UrlHelper _urlHelper; + + public ForumSearchService( + ICommonServices services, + IIndexManager indexManager, + Lazy forumService, + ILogger logger, + UrlHelper urlHelper) + { + _services = services; + _indexManager = indexManager; + _forumService = forumService; + _logger = logger; + _urlHelper = urlHelper; + } + + /// + /// Bypasses the index provider and directly searches in the database. + /// + /// Search query. + /// Forum search result. + protected virtual ForumSearchResult SearchDirect(ForumSearchQuery searchQuery) + { + // Fallback to linq search. + var linqForumSearchService = _services.Container.ResolveNamed("linq"); + var result = linqForumSearchService.Search(searchQuery, true); + + return result; + } + + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) + { + Guard.NotNull(searchQuery, nameof(searchQuery)); + Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); + + var provider = _indexManager.GetIndexProvider("Forum"); + + if (!direct && provider != null) + { + var indexStore = provider.GetIndexStore("Forum"); + if (indexStore.Exists) + { + var searchEngine = provider.GetSearchEngine(indexStore, searchQuery); + var stepPrefix = searchEngine.GetType().Name + " - "; + var totalCount = 0; + string[] spellCheckerSuggestions = null; + IEnumerable searchHits; + Func> hitsFactory = null; + IDictionary facets = null; + + _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); + + if (searchQuery.Take > 0) + { + using (_services.Chronometer.Step(stepPrefix + "Count")) + { + totalCount = searchEngine.Count(); + // Fix paging boundaries. + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) + { + searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + } + + using (_services.Chronometer.Step(stepPrefix + "Hits")) + { + searchHits = searchEngine.Search(); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + using (_services.Chronometer.Step(stepPrefix + "Collect")) + { + var postIds = searchHits.Select(x => x.EntityId).ToArray(); + hitsFactory = () => _forumService.Value.GetPostsByIds(postIds); + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Facets")) + { + facets = searchEngine.GetFacetMap(); + } + } + catch (Exception ex) + { + _logger.Error(ex); + } + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) + { + try + { + using (_services.Chronometer.Step(stepPrefix + "Spellcheck")) + { + spellCheckerSuggestions = searchEngine.CheckSpelling(); + } + } + catch (Exception ex) + { + // Spell checking should not break the search. + _logger.Error(ex); + } + } + + var result = new ForumSearchResult( + searchEngine, + searchQuery, + totalCount, + hitsFactory, + spellCheckerSuggestions, + facets); + + _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); + + return result; + } + else if (searchQuery.Origin.IsCaseInsensitiveEqual("Boards/Search")) + { + IndexingRequiredNotification(_services, _urlHelper); + } + } + + return SearchDirect(searchQuery); + } + + public IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null) + { + var linqForumSearchService = _services.Container.ResolveNamed("linq"); + return linqForumSearchService.PrepareQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs new file mode 100644 index 0000000000..e6a7f135af --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/IForumSearchService.cs @@ -0,0 +1,24 @@ +using System.Linq; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Services.Search +{ + public partial interface IForumSearchService + { + /// + /// Searches for forum posts. + /// + /// Search term, filters and other parameters used for searching. + /// Bypasses the index provider (if available) and directly searches in the database. + /// Forum search result. + ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false); + + /// + /// Builds a forum post query using linq search. + /// + /// Search term, filters and other parameters used for searching. + /// Optional query used to build the forum post query. + /// Forum post queryable. + IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs new file mode 100644 index 0000000000..970a5566f8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Localization; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Customers; +using SmartStore.Services.Forums; +using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; + +namespace SmartStore.Services.Search +{ + public partial class LinqForumSearchService : SearchServiceBase, IForumSearchService + { + private readonly IRepository _forumPostRepository; + private readonly IRepository _storeMappingRepository; + private readonly IForumService _forumService; + private readonly ICommonServices _services; + private readonly CustomerSettings _customerSettings; + + public LinqForumSearchService( + IRepository forumPostRepository, + IRepository storeMappingRepository, + IForumService forumService, + ICommonServices services, + CustomerSettings customerSettings) + { + _forumPostRepository = forumPostRepository; + _storeMappingRepository = storeMappingRepository; + _forumService = forumService; + _services = services; + _customerSettings = customerSettings; + + T = NullLocalizer.Instance; + QuerySettings = DbQuerySettings.Default; + } + + public Localizer T { get; set; } + public DbQuerySettings QuerySettings { get; set; } + + protected virtual IQueryable GetPostQuery(ForumSearchQuery searchQuery, IQueryable baseQuery) + { + // Post query. + var ordered = false; + var t = searchQuery.Term; + var cnf = _customerSettings.CustomerNameFormat; + var fields = searchQuery.Fields; + var filters = new List(); + var query = baseQuery ?? _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic); + + // Apply search term. + if (t.HasValue() && fields != null && fields.Length != 0 && fields.Any(x => x.HasValue())) + { + if (searchQuery.Mode == SearchMode.StartsWith) + { + query = query.Where(x => + (fields.Contains("subject") && x.ForumTopic.Subject.StartsWith(t)) || + (fields.Contains("text") && x.Text.StartsWith(t)) || + (fields.Contains("username") && ( + cnf == CustomerNameFormat.ShowEmails ? x.Customer.Email.StartsWith(t) : + cnf == CustomerNameFormat.ShowUsernames ? x.Customer.Username.StartsWith(t) : + cnf == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.StartsWith(t) : + x.Customer.FullName.StartsWith(t)) + )); + } + else + { + query = query.Where(x => + (fields.Contains("subject") && x.ForumTopic.Subject.Contains(t)) || + (fields.Contains("text") && x.Text.Contains(t)) || + (fields.Contains("username") && ( + cnf == CustomerNameFormat.ShowEmails ? x.Customer.Email.Contains(t) : + cnf == CustomerNameFormat.ShowUsernames ? x.Customer.Username.Contains(t) : + cnf == CustomerNameFormat.ShowFirstName ? x.Customer.FirstName.Contains(t) : + x.Customer.FullName.Contains(t)) + )); + } + } + + // Filters. + FlattenFilters(searchQuery.Filters, filters); + + foreach (IAttributeSearchFilter filter in filters) + { + var rangeFilter = filter as IRangeSearchFilter; + + if (filter.FieldName == "id") + { + if (rangeFilter != null) + { + var lower = filter.Term as int?; + var upper = rangeFilter.UpperTerm as int?; + + if (lower.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.Id >= lower.Value); + else + query = query.Where(x => x.Id > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesUpper) + query = query.Where(x => x.Id <= upper.Value); + else + query = query.Where(x => x.Id < upper.Value); + } + } + } + else if (filter.FieldName == "forumid") + { + query = query.Where(x => x.ForumTopic.ForumId == (int)filter.Term); + } + else if (filter.FieldName == "customerid") + { + query = query.Where(x => x.CustomerId == (int)filter.Term); + } + else if (filter.FieldName == "createdon") + { + if (rangeFilter != null) + { + var lower = filter.Term as DateTime?; + var upper = rangeFilter.UpperTerm as DateTime?; + + if (lower.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.CreatedOnUtc >= lower.Value); + else + query = query.Where(x => x.CreatedOnUtc > lower.Value); + } + + if (upper.HasValue) + { + if (rangeFilter.IncludesLower) + query = query.Where(x => x.CreatedOnUtc <= upper.Value); + else + query = query.Where(x => x.CreatedOnUtc < upper.Value); + } + } + } + else if (filter.FieldName == "storeid") + { + if (!QuerySettings.IgnoreMultiStore) + { + var storeId = (int)filter.Term; + if (storeId != 0) + { + query = + from p in query + join sm in _storeMappingRepository.TableUntracked on new { eid = p.ForumTopic.Forum.ForumGroupId, ename = "ForumGroup" } + equals new { eid = sm.EntityId, ename = sm.EntityName } into gsm + from sm in gsm.DefaultIfEmpty() + where !p.ForumTopic.Forum.ForumGroup.LimitedToStores || sm.StoreId == storeId + select p; + } + } + } + } + + query = + from p in query + group p by p.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + + // Sorting. + foreach (var sort in searchQuery.Sorting) + { + if (sort.FieldName == "subject") + { + query = OrderBy(ref ordered, query, x => x.ForumTopic.Subject, sort.Descending); + } + else if (sort.FieldName == "username") + { + switch (cnf) + { + case CustomerNameFormat.ShowEmails: + query = OrderBy(ref ordered, query, x => x.Customer.Email, sort.Descending); + break; + case CustomerNameFormat.ShowUsernames: + query = OrderBy(ref ordered, query, x => x.Customer.Username, sort.Descending); + break; + case CustomerNameFormat.ShowFirstName: + query = OrderBy(ref ordered, query, x => x.Customer.FirstName, sort.Descending); + break; + default: + query = OrderBy(ref ordered, query, x => x.Customer.FullName, sort.Descending); + break; + } + } + else if (sort.FieldName == "createdon") + { + // We want to sort by ForumPost.CreatedOnUtc, not ForumTopic.CreatedOnUtc. + query = OrderBy(ref ordered, query, x => x.ForumTopic.LastPostTime, sort.Descending); + } + else if (sort.FieldName == "numposts") + { + query = OrderBy(ref ordered, query, x => x.ForumTopic.NumPosts, sort.Descending); + } + } + + if (!ordered) + { + query = query + .OrderByDescending(x => x.ForumTopic.TopicTypeId) + .ThenByDescending(x => x.ForumTopic.LastPostTime) + .ThenByDescending(x => x.TopicId); + } + + return query; + } + + protected virtual IDictionary GetFacets(ForumSearchQuery searchQuery, int totalHits) + { + var result = new Dictionary(); + var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; + var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; + + foreach (var key in searchQuery.FacetDescriptors.Keys) + { + var descriptor = searchQuery.FacetDescriptors[key]; + var facets = new List(); + var kind = FacetGroup.GetKindByKey("Forum", key); + + if (kind == FacetGroupKind.Forum) + { + var enoughFacets = false; + var groups = _forumService.GetAllForumGroups(storeId); + + foreach (var group in groups) + { + foreach (var forum in group.Forums) + { + facets.Add(new Facet(new FacetValue(forum.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(forum.Id)), + Label = forum.GetLocalized(x => x.Name, languageId), + DisplayOrder = forum.DisplayOrder + })); + + if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) + { + enoughFacets = true; + break; + } + } + + if (enoughFacets) + { + break; + } + } + } + else if (kind == FacetGroupKind.Customer) + { + // Get customers with most posts. + var customerQuery = FacetUtility.GetCustomersByNumberOfPosts( + _forumPostRepository, + _storeMappingRepository, + QuerySettings.IgnoreMultiStore ? 0 : storeId, + descriptor.MinHitCount); + + // Limit the result. Do not allow to get all customers. + var maxChoices = descriptor.MaxChoicesCount > 0 ? descriptor.MaxChoicesCount : 20; + var customers = customerQuery.Take(maxChoices * 3).ToList(); + + foreach (var customer in customers) + { + var name = customer.FormatUserName(_customerSettings, T, true); + if (name.HasValue()) + { + facets.Add(new Facet(new FacetValue(customer.Id, IndexTypeCode.Int32) + { + IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(customer.Id)), + Label = name, + DisplayOrder = 0 + })); + if (facets.Count >= maxChoices) + { + break; + } + } + } + } + else if (kind == FacetGroupKind.Date) + { + foreach (var value in descriptor.Values) + { + facets.Add(new Facet(value)); + } + } + + if (facets.Any(x => x.Published)) + { + //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); + + var group = new FacetGroup( + "Forum", + key, + descriptor.Label, + descriptor.IsMultiSelect, + false, + descriptor.DisplayOrder, + facets.OrderBy(descriptor)) + { + IsScrollable = facets.Count > 14 + }; + + result.Add(key, group); + } + } + + return result; + } + + public ForumSearchResult Search(ForumSearchQuery searchQuery, bool direct = false) + { + _services.EventPublisher.Publish(new ForumSearchingEvent(searchQuery)); + + var totalHits = 0; + Func> hitsFactory = null; + IDictionary facets = null; + + if (searchQuery.Take > 0) + { + var query = GetPostQuery(searchQuery, null); + totalHits = query.Count(); + + // Fix paging boundaries. + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalHits) + { + searchQuery.Slice((totalHits / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + query = query + .Skip(searchQuery.PageIndex * searchQuery.Take) + .Take(searchQuery.Take); + + var ids = query.Select(x => x.Id).ToArray(); + hitsFactory = () => _forumService.GetPostsByIds(ids); + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets) && searchQuery.FacetDescriptors.Any()) + { + facets = GetFacets(searchQuery, totalHits); + } + } + + var result = new ForumSearchResult( + null, + searchQuery, + totalHits, + hitsFactory, + null, + facets); + + _services.EventPublisher.Publish(new ForumSearchedEvent(searchQuery, result)); + + return result; + } + + public IQueryable PrepareQuery(ForumSearchQuery searchQuery, IQueryable baseQuery = null) + { + return GetPostQuery(searchQuery, baseQuery); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..ccded4cd3f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryAliasMapper.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Caching; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Configuration; +using SmartStore.Services.Localization; +using SmartStore.Services.Search.Extensions; + +namespace SmartStore.Services.Search.Modelling +{ + public class ForumSearchQueryAliasMapper : IForumSearchQueryAliasMapper + { + private const string ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY = "search.forum.commonfacet.alias.kind.mappings.all"; + + private readonly ICacheManager _cacheManager; + private readonly ISettingService _settingService; + private readonly ILanguageService _languageService; + + public ForumSearchQueryAliasMapper( + ICacheManager cacheManager, + ISettingService settingService, + ILanguageService languageService) + { + _cacheManager = cacheManager; + _settingService = settingService; + _languageService = languageService; + } + + protected virtual IDictionary GetCommonFacetAliasByGroupKindMappings() + { + return _cacheManager.Get(ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY, () => + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var groupKinds = new FacetGroupKind[] + { + FacetGroupKind.Forum, + FacetGroupKind.Customer, + FacetGroupKind.Date + }; + + foreach (var language in _languageService.GetAllLanguages()) + { + foreach (var groupKind in groupKinds) + { + var key = FacetUtility.GetFacetAliasSettingKey(groupKind, language.Id, "Forum"); + var value = _settingService.GetSettingByKey(key); + if (value.HasValue()) + { + result.Add(key, value); + } + } + } + + return result; + }); + } + + public void ClearCommonFacetCache() + { + _cacheManager.Remove(ALL_FORUM_COMMONFACET_ALIAS_BY_KIND_KEY); + } + + public string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId) + { + var mappings = GetCommonFacetAliasByGroupKindMappings(); + + return mappings.Get(FacetUtility.GetFacetAliasSettingKey(kind, languageId, "Forum")); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs new file mode 100644 index 0000000000..50918174c6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Routing; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Localization; +using SmartStore.Core.Search; +using SmartStore.Core.Search.Facets; +using SmartStore.Services.Common; +using SmartStore.Services.Search.Extensions; + +namespace SmartStore.Services.Search.Modelling +{ + /* + TOKENS: + =============================== + q - Search term + i - Page index + o - Order by + f - Forum + c - Customer + d - Date + */ + + public class ForumSearchQueryFactory : SearchQueryFactoryBase, IForumSearchQueryFactory + { + protected readonly ICommonServices _services; + protected readonly IForumSearchQueryAliasMapper _forumSearchQueryAliasMapper; + protected readonly IGenericAttributeService _genericAttributeService; + protected readonly ForumSearchSettings _searchSettings; + protected readonly ForumSettings _forumSettings; + + public ForumSearchQueryFactory( + ICommonServices services, + HttpContextBase httpContext, + IForumSearchQueryAliasMapper forumSearchQueryAliasMapper, + IGenericAttributeService genericAttributeService, + ForumSearchSettings searchSettings, + ForumSettings forumSettings) + : base(httpContext) + { + _services = services; + _forumSearchQueryAliasMapper = forumSearchQueryAliasMapper; + _genericAttributeService = genericAttributeService; + _searchSettings = searchSettings; + _forumSettings = forumSettings; + + T = NullLocalizer.Instance; + QuerySettings = DbQuerySettings.Default; + } + + public Localizer T { get; set; } + public DbQuerySettings QuerySettings { get; set; } + + public ForumSearchQuery Current { get; private set; } + + protected override string[] Tokens => new string[] { "q", "i", "o", "f", "c", "d" }; + + public ForumSearchQuery CreateFromQuery() + { + var ctx = _httpContext; + + if (ctx.Request == null) + return null; + + var routeData = ctx.Request.RequestContext.RouteData; + var area = routeData.GetAreaName(); + var controller = routeData.GetRequiredString("controller"); + var action = routeData.GetRequiredString("action"); + var origin = "{0}{1}/{2}".FormatInvariant(area == null ? "" : area + "/", controller, action); + var fields = new List { "subject" }; + var term = GetValueFor("q"); + var isInstantSearch = origin.IsCaseInsensitiveEqual("Boards/InstantSearch"); + + fields.AddRange(_searchSettings.SearchFields); + + var query = new ForumSearchQuery(fields.ToArray(), term, _searchSettings.SearchMode) + .OriginatesFrom(origin) + .WithLanguage(_services.WorkContext.WorkingLanguage) + .WithCurrency(_services.WorkContext.WorkingCurrency) + .BuildFacetMap(!isInstantSearch); + + // Store. + if (!QuerySettings.IgnoreMultiStore) + { + query.HasStoreId(_services.StoreContext.CurrentStore.Id); + } + + // Instant-Search never uses these filter parameters. + if (!isInstantSearch) + { + ConvertPagingSorting(query, routeData, origin); + ConvertForum(query, routeData, origin); + ConvertCustomer(query, routeData, origin); + ConvertDate(query, routeData, origin); + } + + OnConverted(query, routeData, origin); + + Current = query; + return query; + } + + protected virtual void ConvertPagingSorting(ForumSearchQuery query, RouteData routeData, string origin) + { + var index = Math.Max(1, GetValueFor("i") ?? 1); + var size = GetPageSize(query, routeData, origin); + query.Slice((index - 1) * size, size); + + if (_forumSettings.AllowSorting) + { + var orderBy = GetValueFor("o"); + if (orderBy == null || orderBy == ForumTopicSorting.Initial) + { + orderBy = _searchSettings.DefaultSortOrder; + } + + query.SortBy(orderBy.Value); + query.CustomData["CurrentSortOrder"] = orderBy.Value; + } + } + + protected virtual int GetPageSize(ForumSearchQuery query, RouteData routeData, string origin) + { + return _forumSettings.SearchResultsPageSize; + } + + private void AddFacet( + ForumSearchQuery query, + FacetGroupKind kind, + bool isMultiSelect, + FacetSorting sorting, + Action addValues) + { + string fieldName; + var displayOrder = 0; + + switch (kind) + { + case FacetGroupKind.Forum: + fieldName = "forumid"; + displayOrder = _searchSettings.ForumDisplayOrder; + break; + case FacetGroupKind.Customer: + fieldName = "customerid"; + displayOrder = _searchSettings.CustomerDisplayOrder; + break; + case FacetGroupKind.Date: + fieldName = "createdon"; + displayOrder = _searchSettings.DateDisplayOrder; + break; + default: + throw new SmartException($"Unknown field name for facet group '{kind.ToString()}'"); + } + + var descriptor = new FacetDescriptor(fieldName); + descriptor.Label = _services.Localization.GetResource(FacetUtility.GetLabelResourceKey(kind) ?? kind.ToString()); + descriptor.IsMultiSelect = isMultiSelect; + descriptor.DisplayOrder = displayOrder; + descriptor.OrderBy = sorting; + descriptor.MinHitCount = _searchSettings.FilterMinHitCount; + descriptor.MaxChoicesCount = _searchSettings.FilterMaxChoicesCount; + + addValues(descriptor); + query.WithFacet(descriptor); + } + + protected virtual void ConvertForum(ForumSearchQuery query, RouteData routeData, string origin) + { + List ids; + + if (GetValueFor(query, "f", FacetGroupKind.Forum, out ids) && ids != null && ids.Any()) + { + query.WithForumIds(ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Forum, true, FacetSorting.HitsDesc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertCustomer(ForumSearchQuery query, RouteData routeData, string origin) + { + List ids; + + if (GetValueFor(query, "c", FacetGroupKind.Customer, out ids) && ids != null && ids.Any()) + { + query.WithCustomerIds(ids.ToArray()); + } + + AddFacet(query, FacetGroupKind.Customer, true, FacetSorting.HitsDesc, descriptor => + { + if (ids != null) + { + foreach (var id in ids) + { + descriptor.AddValue(new FacetValue(id, IndexTypeCode.Int32) + { + IsSelected = true + }); + } + } + }); + } + + protected virtual void ConvertDate(ForumSearchQuery query, RouteData routeData, string origin) + { + string date; + DateTime? fromUtc = null; + DateTime? toUtc = null; + + if (GetValueFor(query, "d", FacetGroupKind.Date, out date) && TryParseRange(date, out fromUtc, out toUtc)) + { + if (fromUtc.HasValue && toUtc.HasValue && fromUtc > toUtc) + { + var tmp = fromUtc; + fromUtc = toUtc; + toUtc = tmp; + } + + if (fromUtc.HasValue || toUtc.HasValue) + { + query.CreatedBetween(fromUtc, toUtc); + } + } + + AddFacet(query, FacetGroupKind.Date, false, FacetSorting.DisplayOrder, descriptor => + { + AddDates(descriptor, fromUtc, toUtc); + }); + } + + protected virtual void OnConverted(ForumSearchQuery query, RouteData routeData, string origin) + { + } + + protected virtual bool GetValueFor(ForumSearchQuery query, string key, FacetGroupKind kind, out T value) + { + return GetValueFor(_forumSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); + } + + protected virtual void AddDates(FacetDescriptor descriptor, DateTime? selectedFrom, DateTime? selectedTo) + { + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var count = 0; + var utcNow = DateTime.UtcNow; + utcNow = new DateTime(utcNow.Year, utcNow.Month, utcNow.Day, 0, 0, 0); + + foreach (ForumDateFilter filter in Enum.GetValues(typeof(ForumDateFilter))) + { + var dt = utcNow.AddDays(-((int)filter)); + + if (filter == ForumDateFilter.LastVisit) + { + var lastVisit = _genericAttributeService.GetAttribute(nameof(Customer), customer.Id, SystemCustomerAttributeNames.LastForumVisit, store.Id); + if (!lastVisit.HasValue) + { + continue; + } + + dt = lastVisit.Value; + } + + var value = selectedTo.HasValue + ? new FacetValue(null, dt, IndexTypeCode.DateTime, false, true) + : new FacetValue(dt, null, IndexTypeCode.DateTime, true, false); + + value.DisplayOrder = ++count; + value.Label = T("Enums.SmartStore.Core.Domain.Forums.ForumDateFilter." + filter.ToString()); + + if (selectedFrom.HasValue) + { + value.IsSelected = dt == selectedFrom.Value; + } + else if (selectedTo.HasValue) + { + value.IsSelected = dt == selectedTo.Value; + } + + descriptor.AddValue(value); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs new file mode 100644 index 0000000000..ec47720bb5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryModelBinder.cs @@ -0,0 +1,42 @@ +using System.Web.Mvc; +using Autofac.Integration.Mvc; + +namespace SmartStore.Services.Search.Modelling +{ + [ModelBinderType(typeof(ForumSearchQuery))] + public class ForumSearchQueryModelBinder : IModelBinder + { + private readonly IForumSearchQueryFactory _factory; + + public ForumSearchQueryModelBinder(IForumSearchQueryFactory factory) + { + _factory = factory; + } + + public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) + { + if (_factory.Current != null) + { + // Don't bind again for current request. + return _factory.Current; + } + + if (controllerContext.IsChildAction) + { + // Never attempt to bind in child actions. We require the binding to happen + // in a parent action. If the child action is part of a request with an already bound + // 'ForumSearchQuery', good for you :-) You'll get an instance, but null otherwise. + return _factory.Current; + } + + var modelType = bindingContext.ModelType; + if (modelType != typeof(ForumSearchQuery)) + { + return new ForumSearchQuery(); + } + + var query = _factory.CreateFromQuery(); + return query; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs new file mode 100644 index 0000000000..70b8785535 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryAliasMapper.cs @@ -0,0 +1,20 @@ +using SmartStore.Core.Search.Facets; + +namespace SmartStore.Services.Search.Modelling +{ + public interface IForumSearchQueryAliasMapper + { + /// + /// Clears all cached common facet mappings + /// + void ClearCommonFacetCache(); + + /// + /// Get the common facet alias by facet group kind + /// + /// Facet group kind + /// Language identifier + /// Common facet alias + string GetCommonFacetAliasByGroupKind(FacetGroupKind kind, int languageId); + } +} diff --git a/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs new file mode 100644 index 0000000000..05ca8c4866 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/IForumSearchQueryFactory.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Services.Search.Modelling +{ + public interface IForumSearchQueryFactory + { + /// + /// Creates a instance from the current + /// by looking up corresponding keys in posted form and/or query string + /// + /// The query object + ForumSearchQuery CreateFromQuery(); + + /// + /// The last created query instance. The MVC model binder uses this property to avoid repeated binding. + /// + ForumSearchQuery Current { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs new file mode 100644 index 0000000000..998503180d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs @@ -0,0 +1,104 @@ +using System.Linq; +using System.Web; +using SmartStore.Collections; +using SmartStore.Utilities; + +namespace SmartStore.Services.Search +{ + public abstract partial class SearchQueryFactoryBase + { + protected readonly HttpContextBase _httpContext; + + private Multimap _aliases; + + protected SearchQueryFactoryBase(HttpContextBase httpContext) + { + _httpContext = httpContext; + } + + protected abstract string[] Tokens { get; } + + protected virtual Multimap Aliases + { + get + { + if (_aliases == null) + { + _aliases = new Multimap(); + + if (_httpContext.Request != null) + { + var tokens = Tokens; + var form = _httpContext.Request.Form; + var query = _httpContext.Request.QueryString; + + if (form != null) + { + foreach (var key in form.AllKeys) + { + if (key.HasValue() && !tokens.Contains(key)) + { + _aliases.AddRange(key, form[key].SplitSafe(",")); + } + } + } + + if (query != null) + { + foreach (var key in query.AllKeys) + { + if (key.HasValue() && !tokens.Contains(key)) + { + _aliases.AddRange(key, query[key].SplitSafe(",")); + } + } + } + } + } + + return _aliases; + } + } + + protected virtual T GetValueFor(string key) + { + T value; + return GetValueFor(key, out value) ? value : default(T); + } + + protected virtual bool GetValueFor(string key, out T value) + { + var strValue = _httpContext.Request?.Unvalidated.Form?[key] ?? _httpContext.Request?.Unvalidated.QueryString?[key]; + + if (strValue.HasValue()) + { + value = strValue.Convert(); + return true; + } + + value = default(T); + return false; + } + + protected virtual bool TryParseRange(string query, out T? min, out T? max) where T : struct + { + min = max = null; + + if (query.IsEmpty()) + { + return false; + } + + // Format: from~to || from[~] || ~to + var arr = query.Split('~').Select(x => x.Trim()).Take(2).ToArray(); + + CommonHelper.TryConvert(arr[0], out min); + if (arr.Length == 2) + { + CommonHelper.TryConvert(arr[1], out max); + } + + return min != null || max != null; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs b/src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs new file mode 100644 index 0000000000..1eb0f59027 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/SearchServiceBase.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Web.Mvc; +using SmartStore.Core.Logging; +using SmartStore.Core.Search; +using SmartStore.Services.Customers; + +namespace SmartStore.Services.Search +{ + public abstract partial class SearchServiceBase + { + protected virtual void FlattenFilters(ICollection filters, List result) + { + foreach (var filter in filters) + { + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + FlattenFilters(combinedFilter.Filters, result); + } + else + { + result.Add(filter); + } + } + } + + protected virtual ISearchFilter FindFilter(ICollection filters, string fieldName) + { + if (fieldName.HasValue()) + { + foreach (var filter in filters) + { + var attributeFilter = filter as IAttributeSearchFilter; + if (attributeFilter != null && attributeFilter.FieldName == fieldName) + { + return attributeFilter; + } + + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + var filter2 = FindFilter(combinedFilter.Filters, fieldName); + if (filter2 != null) + { + return filter2; + } + } + } + } + + return null; + } + + protected virtual List GetIdList(List filters, string fieldName) + { + var result = new List(); + + foreach (IAttributeSearchFilter filter in filters) + { + if (!(filter is IRangeSearchFilter) && filter.FieldName == fieldName) + { + result.Add((int)filter.Term); + } + } + + return result; + } + + protected virtual IOrderedQueryable OrderBy( + ref bool ordered, + IQueryable query, + Expression> keySelector, + bool descending = false) + { + if (ordered) + { + if (descending) + { + return ((IOrderedQueryable)query).ThenByDescending(keySelector); + } + + return ((IOrderedQueryable)query).ThenBy(keySelector); + } + else + { + ordered = true; + + if (descending) + { + return query.OrderByDescending(keySelector); + } + + return query.OrderBy(keySelector); + } + } + + /// + /// Notifies the admin that indexing is required to use the advanced search. + /// + protected virtual void IndexingRequiredNotification(ICommonServices services, UrlHelper urlHelper) + { + if (services.WorkContext.CurrentCustomer.IsAdmin()) + { + var indexingUrl = urlHelper.Action("Indexing", "MegaSearch", new { area = "SmartStore.MegaSearch" }); + var configureUrl = urlHelper.Action("ConfigurePlugin", "Plugin", new { area = "admin", systemName = "SmartStore.MegaSearch" }); + var notification = services.Localization.GetResource("Search.IndexingRequiredNotification").FormatInvariant(indexingUrl, configureUrl); + + services.Notifier.Information(notification); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/Security/AclService.cs b/src/Libraries/SmartStore.Services/Security/AclService.cs index a5374054da..5fb2e87e7f 100644 --- a/src/Libraries/SmartStore.Services/Security/AclService.cs +++ b/src/Libraries/SmartStore.Services/Security/AclService.cs @@ -6,32 +6,38 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Security; +using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Services.Customers; namespace SmartStore.Services.Security { public partial class AclService : IAclService { - private const string ACLRECORD_BY_ENTITYID_NAME_KEY = "aclrecord:entityid-name-{0}-{1}"; - private const string ACLRECORD_PATTERN_KEY = "aclrecord:*"; - + /// + /// 0 = segment (EntityName.IdRange) + /// + const string ACL_SEGMENT_KEY = "acl:range-{0}"; + const string ACL_SEGMENT_PATTERN = "acl:range-*"; private readonly IRepository _aclRecordRepository; - private readonly IWorkContext _workContext; + private readonly Work _workContext; private readonly ICacheManager _cacheManager; + private readonly ICustomerService _customerService; + private bool? _hasActiveAcl; - public AclService(ICacheManager cacheManager, IWorkContext workContext, - IRepository aclRecordRepository) + public AclService( + ICacheManager cacheManager, + Work workContext, + IRepository aclRecordRepository, + ICustomerService customerService) { _cacheManager = cacheManager; _workContext = workContext; _aclRecordRepository = aclRecordRepository; - - QuerySettings = DbQuerySettings.Default; + _customerService = customerService; } - public DbQuerySettings QuerySettings { get; set; } - public bool HasActiveAcl { get @@ -50,8 +56,8 @@ public virtual void DeleteAclRecord(AclRecord aclRecord) _aclRecordRepository.Delete(aclRecord); - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } public virtual AclRecord GetAclRecordById(int aclRecordId) { @@ -85,15 +91,37 @@ public virtual IList GetAclRecordsFor(string entityName, int entityId return aclRecords; } + public virtual void SaveAclMappings(T entity, int[] selectedCustomerRoleIds) where T : BaseEntity, IAclSupported + { + var existingAclRecords = GetAclRecords(entity); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + + foreach (var customerRole in allCustomerRoles) + { + if (selectedCustomerRoleIds != null && selectedCustomerRoleIds.Contains(customerRole.Id)) + { + // New role + if (!existingAclRecords.Any(x => x.CustomerRoleId == customerRole.Id)) + InsertAclRecord(entity, customerRole.Id); + } + else + { + // Removed role + var aclRecordToDelete = existingAclRecords.FirstOrDefault(x => x.CustomerRoleId == customerRole.Id); + if (aclRecordToDelete != null) + DeleteAclRecord(aclRecordToDelete); + } + } + } - public virtual void InsertAclRecord(AclRecord aclRecord) + public virtual void InsertAclRecord(AclRecord aclRecord) { Guard.NotNull(aclRecord, nameof(aclRecord)); _aclRecordRepository.Insert(aclRecord); - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } public virtual void InsertAclRecord(T entity, int customerRoleId) where T : BaseEntity, IAclSupported { @@ -121,8 +149,8 @@ public virtual void UpdateAclRecord(AclRecord aclRecord) _aclRecordRepository.Update(aclRecord); - _cacheManager.RemoveByPattern(ACLRECORD_PATTERN_KEY); - } + ClearCacheSegment(aclRecord.EntityName, aclRecord.EntityId); + } public virtual int[] GetCustomerRoleIdsWithAccess(string entityName, int entityId) { @@ -131,22 +159,19 @@ public virtual int[] GetCustomerRoleIdsWithAccess(string entityName, int entityI if (entityId <= 0) return new int[0]; - string key = string.Format(ACLRECORD_BY_ENTITYID_NAME_KEY, entityId, entityName); - return _cacheManager.Get(key, () => + var cacheSegment = GetCacheSegment(entityName, entityId); + + if (!cacheSegment.TryGetValue(entityId, out var roleIds)) { - var query = from ur in _aclRecordRepository.Table - where ur.EntityId == entityId && - ur.EntityName == entityName - select ur.CustomerRoleId; + return Array.Empty(); + } - var result = query.ToArray(); - return result; - }); + return roleIds; } public bool Authorize(string entityName, int entityId) { - return Authorize(entityName, entityId, _workContext.CurrentCustomer); + return Authorize(entityName, entityId, _workContext.Value.CurrentCustomer); } public virtual bool Authorize(string entityName, int entityId, Customer customer) @@ -159,7 +184,7 @@ public virtual bool Authorize(string entityName, int entityId, Customer customer if (customer == null) return false; - if (QuerySettings.IgnoreAcl) + if (!HasActiveAcl) return true; foreach (var role1 in customer.CustomerRoles.Where(cr => cr.Active)) @@ -177,5 +202,67 @@ public virtual bool Authorize(string entityName, int entityId, Customer customer // no permission granted return false; } + + #region Cache segmenting + + protected virtual IDictionary GetCacheSegment(string entityName, int entityId) + { + Guard.NotEmpty(entityName, nameof(entityName)); + + var segmentKey = GetSegmentKeyPart(entityName, entityId, out var minEntityId, out var maxEntityId); + var cacheKey = BuildCacheSegmentKey(segmentKey); + + return _cacheManager.Get(cacheKey, () => + { + var query = from sm in _aclRecordRepository.TableUntracked + where + sm.EntityId >= minEntityId && + sm.EntityId <= maxEntityId && + sm.EntityName == entityName + select sm; + + var mappings = query.ToLookup(x => x.EntityId, x => x.CustomerRoleId); + + var dict = new Dictionary(mappings.Count); + + foreach (var sm in mappings) + { + dict[sm.Key] = sm.ToArray(); + } + + return dict; + }); + } + + /// + /// Clears the cached segment from the cache + /// + protected virtual void ClearCacheSegment(string entityName, int entityId) + { + try + { + var segmentKey = GetSegmentKeyPart(entityName, entityId); + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey)); + } + catch { } + } + + private string BuildCacheSegmentKey(string segment) + { + return String.Format(ACL_SEGMENT_KEY, segment); + } + + private string GetSegmentKeyPart(string entityName, int entityId) + { + return GetSegmentKeyPart(entityName, entityId, out _, out _); + } + + private string GetSegmentKeyPart(string entityName, int entityId, out int minId, out int maxId) + { + maxId = entityId.GetRange(1000, out minId); + return (entityName + "." + maxId.ToString()).ToLowerInvariant(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Security/IAclService.cs b/src/Libraries/SmartStore.Services/Security/IAclService.cs index d4d9eb905c..b6032be635 100644 --- a/src/Libraries/SmartStore.Services/Security/IAclService.cs +++ b/src/Libraries/SmartStore.Services/Security/IAclService.cs @@ -46,11 +46,19 @@ public partial interface IAclService /// ACL records IList GetAclRecordsFor(string entityName, int entityId); - /// - /// Inserts an ACL record - /// - /// ACL record - void InsertAclRecord(AclRecord aclRecord); + /// + /// Save the ACL mappings for an entity + /// + /// Entity type + /// The entity + /// Array of selected customer role ids with access to the passed entity + void SaveAclMappings(T entity, int[] selectedCustomerRoleIds) where T : BaseEntity, IAclSupported; + + /// + /// Inserts an ACL record + /// + /// ACL record + void InsertAclRecord(AclRecord aclRecord); /// /// Inserts an ACL record diff --git a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs index 93b8e87f68..21f0ce5fb0 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -15,7 +15,7 @@ namespace SmartStore.Services.Seo { public static class SeoExtensions { - #region Product tag + #region Entities /// /// Gets product tag SE (search engine) name @@ -36,15 +36,10 @@ public static string GetSeName(this ProductTag productTag) /// Product tag SE (search engine) name public static string GetSeName(this ProductTag productTag, int languageId) { - if (productTag == null) - throw new ArgumentNullException("productTag"); - string seName = GetSeName(productTag.GetLocalized(x => x.Name, languageId)); - return seName; - } - - #endregion + Guard.NotNull(productTag, nameof(productTag)); - #region Blog / news + return GetSeName((string)productTag.GetLocalized(x => x.Name, languageId)); + } /// /// Gets blog post SE (search engine) name @@ -53,10 +48,9 @@ public static string GetSeName(this ProductTag productTag, int languageId) /// Blog post SE (search engine) name public static string GetSeName(this BlogPost blogPost) { - if (blogPost == null) - throw new ArgumentNullException("blogPost"); - string seName = GetSeName(blogPost.Title); - return seName; + Guard.NotNull(blogPost, nameof(blogPost)); + + return GetSeName(blogPost.Title); } /// @@ -66,10 +60,9 @@ public static string GetSeName(this BlogPost blogPost) /// Blog post SE (search engine) name public static string GetSeName(this BlogPostTag blogPostTag) { - if (blogPostTag == null) - throw new ArgumentNullException("blogPostTag"); - string seName = GetSeName(blogPostTag.Name); - return seName; + Guard.NotNull(blogPostTag, nameof(blogPostTag)); + + return GetSeName(blogPostTag.Name); } /// @@ -79,26 +72,21 @@ public static string GetSeName(this BlogPostTag blogPostTag) /// News item SE (search engine) name public static string GetSeName(this NewsItem newsItem) { - if (newsItem == null) - throw new ArgumentNullException("newsItem"); - string seName = GetSeName(newsItem.Title); - return seName; - } - - #endregion + Guard.NotNull(newsItem, nameof(newsItem)); - #region Forum + return GetSeName(newsItem.Title); + } - /// - /// Gets ForumTopic SE (search engine) name - /// - /// ForumTopic - /// ForumTopic SE (search engine) name - public static string GetSeName(this ForumTopic forumTopic) + /// + /// Gets ForumTopic SE (search engine) name + /// + /// ForumTopic + /// ForumTopic SE (search engine) name + public static string GetSeName(this ForumTopic forumTopic) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); - string seName = GetSeName(forumTopic.Subject); + Guard.NotNull(forumTopic, nameof(forumTopic)); + + string seName = GetSeName(forumTopic.Subject); // Trim SE name to avoid URLs that are too long var maxLength = 100; @@ -110,10 +98,6 @@ public static string GetSeName(this ForumTopic forumTopic) return seName; } - #endregion - - #region ICategoryNode - /// /// Get search engine name for a category node /// @@ -133,7 +117,7 @@ public static string GetSeName(this ICategoryNode node) #endregion - #region General + #region Generic /// /// Get search engine name @@ -278,9 +262,7 @@ public static string ValidateSeName(this T entity, { Guard.NotNull(urlRecordService, nameof(urlRecordService)); Guard.NotNull(seoSettings, nameof(seoSettings)); - - if (entity == null) - throw new ArgumentNullException("entity"); + Guard.NotNull(entity, nameof(entity)); // use name if sename is not specified if (String.IsNullOrWhiteSpace(seName) && !String.IsNullOrWhiteSpace(name)) @@ -320,12 +302,10 @@ public static string ValidateSeName(this T entity, int i = 2; var tempSeName = seName; - extraSlugLookup = extraSlugLookup ?? ((s) => null); - while (true) { // check whether such slug already exists (and that it's not the current entity) - var urlRecord = urlRecordService.GetBySlug(tempSeName) ?? extraSlugLookup(tempSeName); + var urlRecord = urlRecordService.GetBySlug(tempSeName) ?? extraSlugLookup?.Invoke(tempSeName); var reserved1 = urlRecord != null && !(urlRecord.EntityId == entity.Id && urlRecord.EntityName.Equals(entityName, StringComparison.InvariantCultureIgnoreCase)); if (!reserved1 && urlRecord != null && languageId.HasValue) diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index aacd0eb835..86ec6e8ccf 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -16,7 +16,7 @@ public partial class UrlRecordService : ScopedServiceBase, IUrlRecordService /// const string URLRECORD_SEGMENT_KEY = "urlrecord:{0}-lang-{1}"; const string URLRECORD_SEGMENT_PATTERN = "urlrecord:{0}*"; - const string URLRECORD_ALL_PATTERN = "urlrecord:"; + const string URLRECORD_ALL_PATTERN = "urlrecord:*"; const string URLRECORD_ALL_ACTIVESLUGS_KEY = "urlrecord:all-active-slugs"; private readonly IRepository _urlRecordRepository; @@ -42,7 +42,7 @@ public virtual void DeleteUrlRecord(UrlRecord urlRecord) try { // cache - ClearCachedSlugsSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); HasChanges = true; // db @@ -83,7 +83,7 @@ public virtual void InsertUrlRecord(UrlRecord urlRecord) HasChanges = true; // cache - ClearCachedSlugsSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); } catch { } } @@ -99,7 +99,7 @@ public virtual void UpdateUrlRecord(UrlRecord urlRecord) HasChanges = true; // cache - ClearCachedSlugsSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); + ClearCacheSegment(urlRecord.EntityName, urlRecord.EntityId, urlRecord.LanguageId); } catch { } } @@ -155,6 +155,7 @@ public virtual UrlRecord GetBySlug(string slug) var query = from ur in _urlRecordRepository.Table where ur.Slug == slug select ur; + var urlRecord = query.FirstOrDefault(); return urlRecord; } @@ -193,7 +194,7 @@ orderby x.Id descending } else { - var slugs = GetCachedSlugsSegment(entityName, entityId, languageId); + var slugs = GetCacheSegment(entityName, entityId, languageId); if (!slugs.TryGetValue(entityId, out slug)) { @@ -379,14 +380,11 @@ public virtual int CountSlugsPerEntity(string entityName, int entityId) return count; } - protected virtual IDictionary GetCachedSlugsSegment(string entityName, int entityId, int languageId) + protected virtual IDictionary GetCacheSegment(string entityName, int entityId, int languageId) { Guard.NotEmpty(entityName, nameof(entityName)); - int minEntityId = 0; - int maxEntityId = 0; - - var segmentKey = GetSegmentKey(entityName, entityId, out minEntityId, out maxEntityId); + var segmentKey = GetSegmentKeyPart(entityName, entityId, out var minEntityId, out var maxEntityId); var cacheKey = BuildCacheSegmentKey(segmentKey, languageId); return _cacheManager.Get(cacheKey, () => @@ -417,12 +415,12 @@ orderby ur.Id descending /// /// Clears the cached segment from the cache /// - protected virtual void ClearCachedSlugsSegment(string entityName, int entityId, int? languageId = null) + protected virtual void ClearCacheSegment(string entityName, int entityId, int? languageId = null) { if (IsInScope) return; - var segmentKey = GetSegmentKey(entityName, entityId); + var segmentKey = GetSegmentKeyPart(entityName, entityId); if (languageId.HasValue && languageId.Value > 0) { @@ -442,26 +440,15 @@ private string BuildCacheSegmentKey(string segment, int languageId) return String.Format(URLRECORD_SEGMENT_KEY, segment, languageId); } - private string GetSegmentKey(string entityName, int entityId) + private string GetSegmentKeyPart(string entityName, int entityId) { - int minId = 0; - int maxId = 0; - - return GetSegmentKey(entityName, entityId, out minId, out maxId); + return GetSegmentKeyPart(entityName, entityId, out _, out _); } - private string GetSegmentKey(string entityName, int entityId, out int minId, out int maxId) + private string GetSegmentKeyPart(string entityName, int entityId, out int minId, out int maxId) { - minId = 0; - maxId = 0; - - // max 1000 values per cache item - var entityRange = Math.Ceiling((decimal)entityId / 1000) * 1000; - - maxId = (int)entityRange; - minId = maxId - 999; - - return (entityName + "." + entityRange.ToString()).ToLowerInvariant(); + maxId = entityId.GetRange(500, out minId); + return (entityName + "." + maxId.ToString()).ToLowerInvariant(); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs index 5950c9e11c..b68b641ce6 100644 --- a/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs @@ -174,7 +174,7 @@ public string GetSitemap(int? index = null) /// A collection of XML sitemap documents. protected IList Generate() { - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; + var protocol = _services.StoreContext.CurrentStore.ForceSslForAllPages ? "https" : "http"; var nodes = new List(); @@ -215,7 +215,7 @@ protected IList Generate() protected virtual List GetSiteMapDocuments(IReadOnlyCollection nodes) { - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; + var protocol = _services.StoreContext.CurrentStore.ForceSslForAllPages ? "https" : "http"; int siteMapCount = (int)Math.Ceiling(nodes.Count / (double)MaximumSiteMapNodeCount); CheckSitemapCount(siteMapCount); @@ -400,7 +400,10 @@ protected virtual IEnumerable GetManufacturerNodes(string protoc protected virtual IEnumerable GetTopicNodes(string protocol) { - var topics = _topicService.GetAllTopics(_services.StoreContext.CurrentStore.Id).ToList().FindAll(t => t.IncludeInSitemap && !t.RenderAsWidget); + var topics = _topicService.GetAllTopics(_services.StoreContext.CurrentStore.Id).AlterQuery(q => + { + return q.Where(t => t.IncludeInSitemap && !t.RenderAsWidget); + }); _services.DbContext.DetachAll(); @@ -408,7 +411,7 @@ protected virtual IEnumerable GetTopicNodes(string protocol) { var node = new XmlSitemapNode { - Loc = _urlHelper.RouteUrl("Topic", new { SystemName = x.SystemName }, protocol), + Loc = _urlHelper.RouteUrl("Topic", new { SeName = x.GetSeName() }, protocol), LastMod = DateTime.UtcNow, //ChangeFreq = ChangeFrequency.Weekly, //Priority = 0.8f diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 51c293fbed..54a0760a18 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -33,6 +33,7 @@ prompt 4 false + latest pdbonly @@ -42,6 +43,7 @@ prompt 4 false + latest true @@ -51,6 +53,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest true @@ -60,6 +63,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest @@ -105,6 +109,9 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + ..\..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll + ..\..\packages\ncrontab.3.3.0\lib\net35\NCrontab.dll True @@ -117,6 +124,9 @@ ..\..\packages\NReco.PdfGenerator.1.1.15\lib\net20\NReco.PdfGenerator.dll True + + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + ..\..\packages\PreMailer.Net.1.5.5\lib\net45\PreMailer.Net.dll @@ -198,7 +208,12 @@ - + + + + + + @@ -270,9 +285,11 @@ + + @@ -309,6 +326,7 @@ + @@ -403,7 +421,6 @@ - @@ -519,20 +536,33 @@ - + - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + + + + @@ -581,6 +611,7 @@ + diff --git a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs index ef4f36513c..04bf8acfb0 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs @@ -10,15 +10,19 @@ namespace SmartStore.Services.Stores { public partial class StoreMappingService : IStoreMappingService { - private const string STOREMAPPING_BY_ENTITYID_NAME_KEY = "storemapping:entityid-name-{0}-{1}"; - private const string STOREMAPPING_PATTERN_KEY = "storemapping:*"; + /// + /// 0 = segment (EntityName.IdRange) + /// + const string STOREMAPPING_SEGMENT_KEY = "storemapping:range-{0}"; + const string STOREMAPPING_SEGMENT_PATTERN = "storemapping:range-*"; private readonly IRepository _storeMappingRepository; private readonly IStoreContext _storeContext; private readonly IStoreService _storeService; private readonly ICacheManager _cacheManager; - public StoreMappingService(ICacheManager cacheManager, + public StoreMappingService( + ICacheManager cacheManager, IStoreContext storeContext, IStoreService storeService, IRepository storeMappingRepository) @@ -39,7 +43,7 @@ public virtual void DeleteStoreMapping(StoreMapping storeMapping) _storeMappingRepository.Delete(storeMapping); - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } public virtual StoreMapping GetStoreMappingById(int storeMappingId) @@ -106,7 +110,7 @@ public virtual void InsertStoreMapping(StoreMapping storeMapping) _storeMappingRepository.Insert(storeMapping); - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } public virtual void InsertStoreMapping(T entity, int storeId) where T : BaseEntity, IStoreMappingSupported @@ -135,7 +139,7 @@ public virtual void UpdateStoreMapping(StoreMapping storeMapping) _storeMappingRepository.Update(storeMapping); - _cacheManager.RemoveByPattern(STOREMAPPING_PATTERN_KEY); + ClearCacheSegment(storeMapping.EntityName, storeMapping.EntityId); } public virtual int[] GetStoresIdsWithAccess(string entityName, int entityId) @@ -145,17 +149,14 @@ public virtual int[] GetStoresIdsWithAccess(string entityName, int entityId) if (entityId <= 0) return new int[0]; - string key = string.Format(STOREMAPPING_BY_ENTITYID_NAME_KEY, entityId, entityName); - return _cacheManager.Get(key, () => + var cacheSegment = GetCacheSegment(entityName, entityId); + + if (!cacheSegment.TryGetValue(entityId, out var storeIds)) { - var query = from sm in _storeMappingRepository.Table - where sm.EntityId == entityId && - sm.EntityName == entityName - select sm.StoreId; + return Array.Empty(); + } - var result = query.ToArray(); - return result; - }); + return storeIds; } public bool Authorize(string entityName, int entityId) @@ -177,17 +178,70 @@ public virtual bool Authorize(string entityName, int entityId, int storeId) if (QuerySettings.IgnoreMultiStore) return true; - foreach (var storeIdWithAccess in GetStoresIdsWithAccess(entityName, entityId)) + // Permission granted only when the id list contains the passed storeId + return GetStoresIdsWithAccess(entityName, entityId).Any(x => x == storeId); + } + + #region Cache segmenting + + protected virtual IDictionary GetCacheSegment(string entityName, int entityId) + { + Guard.NotEmpty(entityName, nameof(entityName)); + + var segmentKey = GetSegmentKeyPart(entityName, entityId, out var minEntityId, out var maxEntityId); + var cacheKey = BuildCacheSegmentKey(segmentKey); + + return _cacheManager.Get(cacheKey, () => { - if (storeId == storeIdWithAccess) + var query = from sm in _storeMappingRepository.TableUntracked + where + sm.EntityId >= minEntityId && + sm.EntityId <= maxEntityId && + sm.EntityName == entityName + select sm; + + var mappings = query.ToLookup(x => x.EntityId, x => x.StoreId); + + var dict = new Dictionary(mappings.Count); + + foreach (var sm in mappings) { - // yes, we have such permission - return true; + dict[sm.Key] = sm.ToArray(); } + + return dict; + }); + } + + /// + /// Clears the cached segment from the cache + /// + protected virtual void ClearCacheSegment(string entityName, int entityId) + { + try + { + var segmentKey = GetSegmentKeyPart(entityName, entityId); + _cacheManager.Remove(BuildCacheSegmentKey(segmentKey)); } + catch { } + } + + private string BuildCacheSegmentKey(string segment) + { + return String.Format(STOREMAPPING_SEGMENT_KEY, segment); + } - // no permission granted - return false; + private string GetSegmentKeyPart(string entityName, int entityId) + { + return GetSegmentKeyPart(entityName, entityId, out _, out _); } + + private string GetSegmentKeyPart(string entityName, int entityId, out int minId, out int maxId) + { + maxId = entityId.GetRange(1000, out minId); + return (entityName + "." + maxId.ToString()).ToLowerInvariant(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Stores/StoreService.cs b/src/Libraries/SmartStore.Services/Stores/StoreService.cs index e1bbcd8b6f..75e7f86528 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -116,7 +116,7 @@ public string GetHost(Store store, bool? secure = null) { Guard.NotNull(store, nameof(store)); - return store.GetHost(secure ?? _securitySettings.ForceSslForAllPages); + return store.GetHost(secure ?? store.ForceSslForAllPages); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs index 8ae6a15444..3ff6f7c69a 100644 --- a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SmartStore.Core; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks @@ -42,25 +43,6 @@ public partial interface IScheduleTaskService /// Tasks IList GetPendingTasks(); - /// - /// Gets a value indicating whether at least one task is running currently. - /// - /// - bool HasRunningTasks(); - - /// - /// Gets a value indicating whether a task is currently running - /// - /// A identifier - /// true if the task is running, false otherwise - bool IsTaskRunning(int taskId); - - /// - /// Gets a list of currently running instances. - /// - /// Tasks - IList GetRunningTasks(); - /// /// Inserts a task /// @@ -97,9 +79,94 @@ public partial interface IScheduleTaskService /// ScheduleTask /// The next schedule or null if the task is disabled DateTime? GetNextSchedule(ScheduleTask task); + + #region Schedule task history + + /// + /// Gets a list of entries. + /// + /// Page index. + /// Page size. + /// Task identifier. + /// Filter by current machine. + /// Return the last history entry only. + /// Filter by running entries. + /// entries. + IPagedList GetHistoryEntries( + int pageIndex, + int pageSize, + int taskId = 0, + bool forCurrentMachine = false, + bool lastEntryOnly = false, + bool? isRunning = null); + + /// + /// Gets a list of entries. + /// + /// Page index. + /// Page size. + /// Schedule task entity. + /// Filter by current machine. + /// Filter by running entries. + /// entries. + IPagedList GetHistoryEntries( + int pageIndex, + int pageSize, + ScheduleTask task, + bool forCurrentMachine = false, + bool? isRunning = null); + + /// + /// Get last history entry by task identifier. + /// + /// Task identifier. + /// Filter by running entries. + /// entry. + ScheduleTaskHistory GetLastHistoryEntryByTaskId(int taskId, bool? isRunning = null); + + /// + /// Get last history entry for a task. + /// + /// Schedule task entity. + /// Filter by running entries. + /// entry. + ScheduleTaskHistory GetLastHistoryEntryByTask(ScheduleTask task, bool? isRunning = null); + + /// + /// Get a history entry by identifier. + /// + /// History entry identifier. + /// entry. + ScheduleTaskHistory GetHistoryEntryById(int id); + + /// + /// Inserts a entry. + /// + /// entry. + void InsertHistoryEntry(ScheduleTaskHistory historyEntry); + + /// + /// Updates a entry. + /// + /// entry. + void UpdateHistoryEntry(ScheduleTaskHistory historyEntry); + + /// + /// Delete a entry. + /// + /// entry. + void DeleteHistoryEntry(ScheduleTaskHistory historyEntry); + + /// + /// Deletes old entries. + /// + /// Number of deleted entries. + int DeleteHistoryEntries(); + + #endregion } - public static class IScheduleTaskServiceExtensions + public static class IScheduleTaskServiceExtensions { public static ScheduleTask GetTaskByType(this IScheduleTaskService service) where T : ITask { diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs index 053cf45937..ef9d7da4f0 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs index 4510bc408a..0070ad48f6 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs @@ -1,10 +1,8 @@ -using System; -using System.Web; +using System.Collections.Generic; using System.Linq; -using System.Collections.Generic; +using System.Web; using SmartStore.Core; using SmartStore.Services.Stores; -using SmartStore.Core.Async; namespace SmartStore.Services.Tasks { diff --git a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs index 2756605ef8..7d5b7373a7 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs @@ -1,28 +1,39 @@ using System; using System.Collections.Generic; -using System.Data.Entity.Infrastructure; +using System.Data.Entity.Core; +using System.Data.SqlClient; using System.Linq; +using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Localization; -using SmartStore.Utilities; -using SmartStore.Services.Helpers; -using System.Data.Entity.Core; -using System.Data.SqlClient; using SmartStore.Core.Logging; -using SmartStore.Core; +using SmartStore.Services.Helpers; +using SmartStore.Utilities; namespace SmartStore.Services.Tasks { public partial class ScheduleTaskService : IScheduleTaskService { private readonly IRepository _taskRepository; - private readonly IDateTimeHelper _dtHelper; - - public ScheduleTaskService(IRepository taskRepository, IDateTimeHelper dtHelper) + private readonly IRepository _taskHistoryRepository; + private readonly IDateTimeHelper _dtHelper; + private readonly IApplicationEnvironment _env; + private readonly Lazy _commonSettings; + + public ScheduleTaskService( + IRepository taskRepository, + IRepository taskHistoryRepository, + IDateTimeHelper dtHelper, + IApplicationEnvironment env, + Lazy commonSettings) { _taskRepository = taskRepository; + _taskHistoryRepository = taskHistoryRepository; _dtHelper = dtHelper; + _env = env; + _commonSettings = commonSettings; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -65,7 +76,7 @@ public virtual ScheduleTask GetTaskByType(string type) } catch (Exception exc) { - // do not throw an exception if the underlying provider failed on Open. + // Do not throw an exception if the underlying provider failed on Open. exc.Dump(); } @@ -90,55 +101,38 @@ public virtual IList GetAllTasks(bool includeDisabled = false) public virtual IList GetPendingTasks() { var now = DateTime.UtcNow; - - var query = from t in _taskRepository.Table - where t.NextRunUtc.HasValue && t.NextRunUtc <= now && t.Enabled - orderby t.NextRunUtc - select t; - - return Retry.Run( - () => query.ToList(), - 3, TimeSpan.FromMilliseconds(100), - RetryOnDeadlockException); + var machineName = _env.MachineName; + + var query = ( + from t in _taskRepository.Table + where t.NextRunUtc.HasValue && t.NextRunUtc <= now && t.Enabled + select new + { + Task = t, + LastEntry = t.ScheduleTaskHistory + .Where(th => !t.RunPerMachine || (t.RunPerMachine && th.MachineName == machineName)) + .OrderByDescending(th => th.StartedOnUtc) + .ThenByDescending(th => th.Id) + .FirstOrDefault() + }); + + var tasks = Retry.Run( + () => query.ToList(), + 3, TimeSpan.FromMilliseconds(100), + RetryOnDeadlockException); + + var pendingTasks = tasks + .Select(x => + { + x.Task.LastHistoryEntry = x.LastEntry; + return x.Task; + }) + .Where(x => x.IsPending) + .ToList(); + + return pendingTasks; } - public virtual bool HasRunningTasks() - { - var query = GetRunningTasksQuery(); - return query.Any(); - } - - public virtual bool IsTaskRunning(int taskId) - { - if (taskId <= 0) - return false; - - var query = GetRunningTasksQuery(); - query.Where(t => t.Id == taskId); - return query.Any(); - } - - public virtual IList GetRunningTasks() - { - var query = GetRunningTasksQuery(); - - return Retry.Run( - () => query.ToList(), - 3, TimeSpan.FromMilliseconds(100), - RetryOnDeadlockException); - } - - private IQueryable GetRunningTasksQuery() - { - var query = from t in _taskRepository.Table - where t.LastStartUtc.HasValue && t.LastStartUtc.Value > (t.LastEndUtc ?? DateTime.MinValue) - orderby t.LastStartUtc - select t; - - return query; - } - - public virtual void InsertTask(ScheduleTask task) { Guard.NotNull(task, nameof(task)); @@ -152,54 +146,7 @@ public virtual void UpdateTask(ScheduleTask task) try { - using (var scope = new DbContextScope(_taskRepository.Context, autoCommit: true)) - { - Retry.Run(() => _taskRepository.Update(task), 3, TimeSpan.FromMilliseconds(50), (attempt, exception) => - { - var ex = exception as DbUpdateConcurrencyException; - if (ex == null) return; - - var entry = ex.Entries.Single(); - var current = (ScheduleTask)entry.CurrentValues.ToObject(); // from current scope - - // When 'StopOnError' is true, the 'Enabled' property could have been set to true on exception. - var prop = entry.Property("Enabled"); - var enabledModified = !prop.CurrentValue.Equals(prop.OriginalValue); - - // Save current cron expression - var cronExpression = task.CronExpression; - - // Fetch Name, CronExpression, Enabled & StopOnError from database - // (these were possibly edited thru the backend) - _taskRepository.Context.ReloadEntity(task); - - // Do we have to reschedule the task? - var cronModified = cronExpression != task.CronExpression; - - // Copy execution specific data from current to reloaded entity - task.LastEndUtc = current.LastEndUtc; - task.LastError = current.LastError; - task.LastStartUtc = current.LastStartUtc; - task.LastSuccessUtc = current.LastSuccessUtc; - task.ProgressMessage = current.ProgressMessage; - task.ProgressPercent = current.ProgressPercent; - task.NextRunUtc = current.NextRunUtc; - if (enabledModified) - { - task.Enabled = current.Enabled; - } - if (task.NextRunUtc.HasValue && cronModified) - { - // reschedule task - task.NextRunUtc = GetNextSchedule(task); - } - - if (attempt == 3) - { - _taskRepository.Update(task); - } - }); - } + _taskRepository.Update(task); } catch (Exception ex) { @@ -240,13 +187,6 @@ public void CalculateFutureSchedules(IEnumerable tasks, bool isApp task.NextRunUtc = GetNextSchedule(task); if (isAppStart) { - task.ProgressPercent = null; - task.ProgressMessage = null; - if (task.LastEndUtc.GetValueOrDefault() < task.LastStartUtc) - { - task.LastEndUtc = task.LastStartUtc; - task.LastError = T("Admin.System.ScheduleTasks.AbnormalAbort"); - } FixTypeName(task); } else @@ -261,11 +201,49 @@ public void CalculateFutureSchedules(IEnumerable tasks, bool isApp // to commit all changes in one go. _taskRepository.Context.SaveChanges(); } - } + + if (isAppStart) + { + // Normalize task history entries. + // That is, no task can run when the application starts and therefore no entry may be marked as running. + var entries = _taskHistoryRepository.Table + .Where(x => + x.IsRunning || + x.ProgressPercent != null || + !string.IsNullOrEmpty(x.ProgressMessage) || + (x.FinishedOnUtc != null && x.FinishedOnUtc < x.StartedOnUtc) + ) + .ToList(); + + if (entries.Any()) + { + string abnormalAbort = T("Admin.System.ScheduleTasks.AbnormalAbort"); + foreach (var entry in entries) + { + var invalidTimeRange = entry.FinishedOnUtc.HasValue && entry.FinishedOnUtc < entry.StartedOnUtc; + if (invalidTimeRange || entry.IsRunning) + { + entry.Error = abnormalAbort; + } + + entry.IsRunning = false; + entry.ProgressPercent = null; + entry.ProgressMessage = null; + if (invalidTimeRange) + { + entry.FinishedOnUtc = entry.StartedOnUtc; + } + } + + _taskHistoryRepository.UpdateRange(entries); + _taskHistoryRepository.Context.SaveChanges(); + } + } + } private void FixTypeName(ScheduleTask task) { - // in versions prior V3 a double space could exist in ScheduleTask type name + // In versions prior V3 a double space could exist in ScheduleTask type name. if (task.Type.IndexOf(", ") > 0) { task.Type = task.Type.Replace(", ", ", "); @@ -302,9 +280,255 @@ private static void RetryOnDeadlockException(int attemp, Exception ex) if (!isDeadLockException) { - // we only want to retry on deadlock stuff + // We only want to retry on deadlock stuff. throw ex; } } - } + + #region Schedule task history + + protected virtual IQueryable GetHistoryEntriesQuery( + int taskId = 0, + bool forCurrentMachine = false, + bool lastEntryOnly = false, + bool? isRunning = null) + { + var query = _taskHistoryRepository.TableUntracked; + + if (taskId != 0) + { + query = query.Where(x => x.ScheduleTaskId == taskId); + } + if (forCurrentMachine) + { + var machineName = _env.MachineName; + query = query.Where(x => x.MachineName == machineName); + } + if (isRunning.HasValue) + { + query = query.Where(x => x.IsRunning == isRunning.Value); + } + + if (lastEntryOnly) + { + query = + from th in query + group th by th.ScheduleTaskId into grp + select grp + .OrderByDescending(x => x.StartedOnUtc) + .ThenByDescending(x => x.Id) + .FirstOrDefault(); + } + + query = query + .OrderByDescending(x => x.StartedOnUtc) + .ThenByDescending(x => x.Id); + + return query; + } + + protected virtual IQueryable GetHistoryEntriesQuery( + ScheduleTask task, + bool forCurrentMachine = false, + bool? isRunning = null) + { + _taskRepository.Context.LoadCollection( + task, + (ScheduleTask x) => x.ScheduleTaskHistory, + false, + (IQueryable query) => + { + if (forCurrentMachine) + { + var machineName = _env.MachineName; + query = query.Where(x => x.MachineName == machineName); + } + if (isRunning.HasValue) + { + query = query.Where(x => x.IsRunning == isRunning.Value); + } + + query = query + .OrderByDescending(x => x.StartedOnUtc) + .ThenByDescending(x => x.Id); + + return query; + }); + + return task.ScheduleTaskHistory.AsQueryable(); + } + + public virtual IPagedList GetHistoryEntries( + int pageIndex, + int pageSize, + int taskId = 0, + bool forCurrentMachine = false, + bool lastEntryOnly = false, + bool? isRunning = null) + { + var query = GetHistoryEntriesQuery(taskId, forCurrentMachine, lastEntryOnly, isRunning); + var entries = new PagedList(query, pageIndex, pageSize); + return entries; + } + + public virtual IPagedList GetHistoryEntries( + int pageIndex, + int pageSize, + ScheduleTask task, + bool forCurrentMachine = false, + bool? isRunning = null) + { + if (task == null) + { + return new PagedList(new List(), pageIndex, pageSize); + } + + var query = GetHistoryEntriesQuery(task, forCurrentMachine, isRunning); + var entries = new PagedList(query, pageIndex, pageSize); + return entries; + } + + public virtual ScheduleTaskHistory GetLastHistoryEntryByTaskId(int taskId, bool? isRunning = null) + { + if (taskId == 0) + { + return null; + } + + var query = GetHistoryEntriesQuery(taskId, true, false, isRunning); + query = query.Expand(x => x.ScheduleTask); + + var entry = Retry.Run( + () => query.FirstOrDefault(), + 3, TimeSpan.FromMilliseconds(100), + RetryOnDeadlockException); + + return entry; + } + + public virtual ScheduleTaskHistory GetLastHistoryEntryByTask(ScheduleTask task, bool? isRunning = null) + { + if (task == null) + { + return null; + } + + var query = GetHistoryEntriesQuery(task, true, isRunning); + + var entry = Retry.Run( + () => query.FirstOrDefault(), + 3, TimeSpan.FromMilliseconds(100), + RetryOnDeadlockException); + + return entry; + } + + public virtual ScheduleTaskHistory GetHistoryEntryById(int id) + { + if (id == 0) + { + return null; + } + + return _taskHistoryRepository.GetById(id); + } + + public virtual void InsertHistoryEntry(ScheduleTaskHistory historyEntry) + { + Guard.NotNull(historyEntry, nameof(historyEntry)); + + _taskHistoryRepository.Insert(historyEntry); + } + + public virtual void UpdateHistoryEntry(ScheduleTaskHistory historyEntry) + { + Guard.NotNull(historyEntry, nameof(historyEntry)); + + try + { + _taskHistoryRepository.Update(historyEntry); + } + catch (Exception ex) + { + Logger.Error(ex); + // Do not throw. + } + } + + public virtual void DeleteHistoryEntry(ScheduleTaskHistory historyEntry) + { + Guard.NotNull(historyEntry, nameof(historyEntry)); + Guard.IsTrue(!historyEntry.IsRunning, nameof(historyEntry.IsRunning), "Cannot delete a running schedule task history entry."); + + _taskHistoryRepository.Delete(historyEntry); + } + + public virtual int DeleteHistoryEntries() + { + var count = 0; + var idsToDelete = new HashSet(); + + if (_commonSettings.Value.MaxScheduleHistoryAgeInDays > 0) + { + var earliestDate = DateTime.UtcNow.AddDays(-1 * _commonSettings.Value.MaxScheduleHistoryAgeInDays); + var ids = _taskHistoryRepository.TableUntracked + .Where(x => x.StartedOnUtc <= earliestDate && !x.IsRunning) + .Select(x => x.Id) + .ToList(); + + idsToDelete.AddRange(ids); + } + + // We have to group by task otherwise we would only keep entries from very frequently executed tasks. + if (_commonSettings.Value.MaxNumberOfScheduleHistoryEntries > 0) + { + var query = + from th in _taskHistoryRepository.TableUntracked + where !th.IsRunning + group th by th.ScheduleTaskId into grp + select grp + .OrderByDescending(x => x.StartedOnUtc) + .ThenByDescending(x => x.Id) + .Skip(_commonSettings.Value.MaxNumberOfScheduleHistoryEntries) + .Select(x => x.Id); + + var ids = query.SelectMany(x => x).ToList(); + + idsToDelete.AddRange(ids); + } + + try + { + if (idsToDelete.Any()) + { + using (var scope = new DbContextScope(_taskHistoryRepository.Context, autoCommit: false)) + { + var pageIndex = 0; + IPagedList pagedIds = null; + + do + { + pagedIds = new PagedList(idsToDelete, pageIndex++, 100); + + var entries = _taskHistoryRepository.Table + .Where(x => pagedIds.Contains(x.Id)) + .ToList(); + + entries.Each(x => DeleteHistoryEntry(x)); + count += scope.Commit(); + } + while (pagedIds.HasNextPage); + } + } + } + catch (Exception ex) + { + Logger.Error(ex); + } + + return count; + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs index f9de1eb89a..9313933498 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using Autofac; using SmartStore.Core.Data; @@ -8,27 +7,28 @@ namespace SmartStore.Services.Tasks { - /// - /// Provides the context for the Execute method of the interface. - /// - public class TaskExecutionContext + /// + /// Provides the context for the Execute method of the interface. + /// + public class TaskExecutionContext { private readonly IComponentContext _componentContext; - private readonly ScheduleTask _originalTask; + private readonly ScheduleTaskHistory _originalTaskHistory; - internal TaskExecutionContext(IComponentContext componentContext, ScheduleTask originalTask) - { - _componentContext = componentContext; - _originalTask = originalTask; - Parameters = new Dictionary(); - } + internal TaskExecutionContext(IComponentContext componentContext, ScheduleTaskHistory originalTaskHistory) + { + _componentContext = componentContext; + _originalTaskHistory = originalTaskHistory; + Parameters = new Dictionary(); + } - public T Resolve(object key = null) where T : class + public T Resolve(object key = null) where T : class { if (key == null) { return _componentContext.Resolve(); } + return _componentContext.ResolveKeyed(key); } @@ -43,9 +43,9 @@ public T ResolveNamed(string name) where T : class /// public CancellationToken CancellationToken { get; internal set; } - public ScheduleTask ScheduleTask { get; set; } + public ScheduleTaskHistory ScheduleTaskHistory { get; set; } - public IDictionary Parameters { get; set; } + public IDictionary Parameters { get; set; } /// /// Persists a task's progress information to the database @@ -77,21 +77,24 @@ public void SetProgress(int value, int maximum, string message, bool immediately /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. public virtual void SetProgress(int? progress, string message, bool immediately = false) { - if (progress.HasValue) - Guard.InRange(progress.Value, 0, 100, nameof(progress)); + if (progress.HasValue) + { + Guard.InRange(progress.Value, 0, 100, nameof(progress)); + } - // update cloned entity - ScheduleTask.ProgressPercent = progress; - ScheduleTask.ProgressMessage = message; + // Update cloned entity. + ScheduleTaskHistory.ProgressPercent = progress; + ScheduleTaskHistory.ProgressMessage = message; - // update attached entity - _originalTask.ProgressPercent = progress; - _originalTask.ProgressMessage = message; + // Update attached entity. + _originalTaskHistory.ProgressPercent = progress; + _originalTaskHistory.ProgressMessage = message; if (immediately) { - try // dont't let this abort the task on failure - { + // Dont't let this abort the task on failure. + try + { var dbContext = _componentContext.Resolve(); //dbContext.ChangeState(_originalTask, System.Data.Entity.EntityState.Modified); dbContext.SaveChanges(); @@ -122,11 +125,19 @@ public static TaskExecutionContext CreateTransientContext(IComponentContext comp /// A transient context public static TaskExecutionContext CreateTransientContext(IComponentContext componentContext, CancellationToken cancellationToken) { - var originalTask = new ScheduleTask { Name = "Transient", IsHidden = true, Enabled = true }; - var context = new TransientTaskExecutionContext(componentContext, originalTask); - + var originalHistoryEntry = new ScheduleTaskHistory + { + ScheduleTask = new ScheduleTask + { + Name = "Transient", + IsHidden = true, + Enabled = true + } + }; + + var context = new TransientTaskExecutionContext(componentContext, originalHistoryEntry); context.CancellationToken = cancellationToken; - context.ScheduleTask = originalTask.Clone(); + context.ScheduleTaskHistory = originalHistoryEntry.Clone(); return context; } @@ -134,8 +145,8 @@ public static TaskExecutionContext CreateTransientContext(IComponentContext comp internal class TransientTaskExecutionContext : TaskExecutionContext { - public TransientTaskExecutionContext(IComponentContext componentContext, ScheduleTask originalTask) - : base(componentContext, originalTask) + public TransientTaskExecutionContext(IComponentContext componentContext, ScheduleTaskHistory originalHistoryEntry) + : base(componentContext, originalHistoryEntry) { } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 7bd84bd547..bf14291cd6 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,47 +1,40 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using Autofac; using SmartStore.Core; using SmartStore.Core.Async; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.Customers; +using SmartStore.Core.Utilities; namespace SmartStore.Services.Tasks { - public class TaskExecutor : ITaskExecutor + public class TaskExecutor : ITaskExecutor { private readonly IScheduleTaskService _scheduledTaskService; - private readonly IDbContext _dbContext; - private readonly IWorkContext _workContext; private readonly Func _taskResolver; private readonly IComponentContext _componentContext; private readonly IAsyncState _asyncState; + private readonly IApplicationEnvironment _env; - public const string CurrentCustomerIdParamName = "CurrentCustomerId"; + public const string CurrentCustomerIdParamName = "CurrentCustomerId"; public const string CurrentStoreIdParamName = "CurrentStoreId"; public TaskExecutor( IScheduleTaskService scheduledTaskService, - IDbContext dbContext, - ICustomerService customerService, - IWorkContext workContext, IComponentContext componentContext, IAsyncState asyncState, - Func taskResolver) + Func taskResolver, + IApplicationEnvironment env) { _scheduledTaskService = scheduledTaskService; - _dbContext = dbContext; - _workContext = workContext; _componentContext = componentContext; _asyncState = asyncState; _taskResolver = taskResolver; + _env = env; Logger = NullLogger.Instance; T = NullLocalizer.Instance; @@ -55,24 +48,41 @@ public void Execute( IDictionary taskParameters = null, bool throwOnError = false) { - if (task.IsRunning) + if (AsyncRunner.AppShutdownCancellationToken.IsCancellationRequested) + { return; + } - if (AsyncRunner.AppShutdownCancellationToken.IsCancellationRequested) - return; + if (task.LastHistoryEntry == null) + { + // The task was started manually. + task.LastHistoryEntry = _scheduledTaskService.GetLastHistoryEntryByTaskId(task.Id); + } + + if (task?.LastHistoryEntry?.IsRunning == true) + { + return; + } bool faulted = false; bool canceled = false; string lastError = null; ITask instance = null; string stateName = null; - Type taskType = null; + Exception exception = null; - try - { - taskType = Type.GetType(task.Type); + var historyEntry = new ScheduleTaskHistory + { + ScheduleTaskId = task.Id, + IsRunning = true, + MachineName = _env.MachineName, + StartedOnUtc = DateTime.UtcNow + }; + try + { + taskType = Type.GetType(task.Type); if (taskType == null) { Logger.DebugFormat("Invalid scheduled task type: {0}", task.Type.NaIfEmpty()); @@ -83,34 +93,29 @@ public void Execute( if (!PluginManager.IsActivePluginAssembly(taskType.Assembly)) return; - } - catch + + task.ScheduleTaskHistory.Add(historyEntry); + _scheduledTaskService.UpdateTask(task); + } + catch { return; } try { - // create task instance + // Task history entry has been successfully added, now we execute the task. + // Create task instance. instance = _taskResolver(taskType); stateName = task.Id.ToString(); - - // prepare and save entity - task.LastStartUtc = DateTime.UtcNow; - task.LastEndUtc = null; - task.NextRunUtc = null; - task.ProgressPercent = null; - task.ProgressMessage = null; - - _scheduledTaskService.UpdateTask(task); - // create & set a composite CancellationTokenSource which also contains the global app shoutdown token + // Create & set a composite CancellationTokenSource which also contains the global app shoutdown token. var cts = CancellationTokenSource.CreateLinkedTokenSource(AsyncRunner.AppShutdownCancellationToken, new CancellationTokenSource().Token); _asyncState.SetCancelTokenSource(cts, stateName); - var ctx = new TaskExecutionContext(_componentContext, task) + var ctx = new TaskExecutionContext(_componentContext, historyEntry) { - ScheduleTask = task.Clone(), + ScheduleTaskHistory = historyEntry.Clone(), CancellationToken = cts.Token, Parameters = taskParameters ?? new Dictionary() }; @@ -118,57 +123,80 @@ public void Execute( Logger.DebugFormat("Executing scheduled task: {0}", task.Type); instance.Execute(ctx); } - catch (Exception exception) + catch (Exception ex) { + exception = ex; faulted = true; - canceled = exception is OperationCanceledException; - lastError = exception.Message.Truncate(995, "..."); - - if (canceled) - Logger.Warn(exception, T("Admin.System.ScheduleTasks.Cancellation", task.Name)); - else - Logger.Error(exception, string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", exception.Message)); + canceled = ex is OperationCanceledException; + lastError = ex.Message.Truncate(995, "..."); - if (throwOnError) + if (canceled) { - throw; + Logger.Warn(ex, T("Admin.System.ScheduleTasks.Cancellation", task.Name)); + } + else + { + Logger.Error(ex, string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", ex.Message)); } } finally { - task.ProgressPercent = null; - task.ProgressMessage = null; - - var now = DateTime.UtcNow; - task.LastError = lastError; - task.LastEndUtc = now; + var now = DateTime.UtcNow; + var updateTask = false; + + historyEntry.IsRunning = false; + historyEntry.ProgressPercent = null; + historyEntry.ProgressMessage = null; + historyEntry.Error = lastError; + historyEntry.FinishedOnUtc = now; if (faulted) { if ((!canceled && task.StopOnError) || instance == null) { task.Enabled = false; + updateTask = true; } } else { - task.LastSuccessUtc = now; + historyEntry.SucceededOnUtc = now; } - Logger.DebugFormat("Executed scheduled task: {0}. Elapsed: {1} ms.", task.Type, (now - task.LastStartUtc.Value).TotalMilliseconds); + try + { + Logger.DebugFormat("Executed scheduled task: {0}. Elapsed: {1} ms.", task.Type, (now - historyEntry.StartedOnUtc).TotalMilliseconds); + + // Remove from AsyncState. + if (stateName.HasValue()) + { + _asyncState.Remove(stateName); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } - if (task.Enabled) + if (task.Enabled) { task.NextRunUtc = _scheduledTaskService.GetNextSchedule(task); - } + updateTask = true; + } - // remove from AsyncState - if (stateName.HasValue()) - { - _asyncState.Remove(stateName); - } + _scheduledTaskService.UpdateHistoryEntry(historyEntry); + + if (updateTask) + { + _scheduledTaskService.UpdateTask(task); + } - _scheduledTaskService.UpdateTask(task); + Throttle.Check("Delete old schedule task history entries", TimeSpan.FromDays(1), () => _scheduledTaskService.DeleteHistoryEntries() > 0); + } + + if (throwOnError && exception != null) + { + throw exception; } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExtensions.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExtensions.cs new file mode 100644 index 0000000000..475f8104aa --- /dev/null +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExtensions.cs @@ -0,0 +1,31 @@ +using System; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Tasks +{ + public static class TaskExtensions + { + /// + /// Gets whether the schedule task is visible or not. + /// + /// Scheduled task. + /// true task is visible, false task is not visible. + public static bool IsVisible(this ScheduleTask task) + { + Guard.NotNull(task, nameof(task)); + if (task.IsHidden) + { + return false; + } + + var type = Type.GetType(task.Type); + if (type != null) + { + return PluginManager.IsActivePluginAssembly(type.Assembly); + } + + return false; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Topics/ITopicService.cs b/src/Libraries/SmartStore.Services/Topics/ITopicService.cs index 1babca2b5f..ccdd2e3588 100644 --- a/src/Libraries/SmartStore.Services/Topics/ITopicService.cs +++ b/src/Libraries/SmartStore.Services/Topics/ITopicService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using SmartStore.Core; using SmartStore.Core.Domain.Topics; namespace SmartStore.Services.Topics @@ -21,26 +22,28 @@ public partial interface ITopicService /// Topic Topic GetTopicById(int topicId); - /// - /// Gets a topic - /// - /// The topic system name + /// + /// Gets a topic + /// + /// The topic system name /// Store identifier - /// Topic - Topic GetTopicBySystemName(string systemName, int storeId); + /// Whether to check for permission (ACL). If true and check fails, null is returned. + /// Topic + Topic GetTopicBySystemName(string systemName, int storeId = 0, bool checkPermission = true); - /// - /// Gets all topics - /// + /// + /// Gets all topics + /// /// Store identifier; pass 0 to load all records - /// Topics - IList GetAllTopics(int storeId); + /// Whether to load hidden records + /// Topics + IPagedList GetAllTopics(int storeId = 0, int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); - /// - /// Inserts a topic - /// - /// Topic - void InsertTopic(Topic topic); + /// + /// Inserts a topic + /// + /// Topic + void InsertTopic(Topic topic); /// /// Updates the topic diff --git a/src/Libraries/SmartStore.Services/Topics/TopicService.cs b/src/Libraries/SmartStore.Services/Topics/TopicService.cs index 7f0117955a..eb8dfee3a0 100644 --- a/src/Libraries/SmartStore.Services/Topics/TopicService.cs +++ b/src/Libraries/SmartStore.Services/Topics/TopicService.cs @@ -1,28 +1,36 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Topics; -using SmartStore.Core.Events; using SmartStore.Data.Caching; +using SmartStore.Services.Stores; namespace SmartStore.Services.Topics { public partial class TopicService : ITopicService { + private readonly ICommonServices _services; private readonly IRepository _topicRepository; private readonly IRepository _storeMappingRepository; - private readonly IEventPublisher _eventPublisher; + private readonly IStoreMappingService _storeMappingService; + private readonly IRepository _aclRepository; public TopicService( + ICommonServices services, IRepository topicRepository, IRepository storeMappingRepository, - IEventPublisher eventPublisher) + IStoreMappingService storeMappingService, + IRepository aclRepository) { - _topicRepository = topicRepository; + _services = services; + _topicRepository = topicRepository; _storeMappingRepository = storeMappingRepository; - _eventPublisher = eventPublisher; + _storeMappingService = storeMappingService; + _aclRepository = aclRepository; this.QuerySettings = DbQuerySettings.Default; } @@ -44,34 +52,70 @@ public virtual Topic GetTopicById(int topicId) return _topicRepository.GetById(topicId); } - public virtual Topic GetTopicBySystemName(string systemName, int storeId) + public virtual Topic GetTopicBySystemName(string systemName, int storeId = 0, bool checkPermission = true) { - Guard.NotEmpty(systemName, nameof(systemName)); + if (systemName.IsEmpty()) + return null; - var allTopics = GetAllTopics(storeId); + var query = BuildTopicsQuery(systemName, storeId, !checkPermission); + var rolesIdent = checkPermission + ? "0" + : _services.WorkContext.CurrentCustomer.GetRolesIdent(); + + var result = query.FirstOrDefaultCached("db.topic.bysysname-{0}-{1}-{2}".FormatInvariant(systemName, storeId, rolesIdent)); - var topic = allTopics - .OrderBy(x => x.Id) - .FirstOrDefault(x => x.SystemName.IsCaseInsensitiveEqual(systemName)); - - return topic; + return result; } - public virtual IList GetAllTopics(int storeId) + public virtual IPagedList GetAllTopics(int storeId = 0, int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) { - var query = _topicRepository.Table; + var query = BuildTopicsQuery(null, storeId, showHidden); + return new PagedList(query, pageIndex, pageSize); + } + + protected virtual IQueryable BuildTopicsQuery(string systemName, int storeId, bool showHidden = false) + { + var entityName = nameof(Topic); + var joinApplied = false; + + var query = _topicRepository.Table.Where(x => showHidden || x.IsPublished); + + if (systemName.HasValue()) + { + query = query.Where(x => x.SystemName == systemName); + } // Store mapping if (storeId > 0 && !QuerySettings.IgnoreMultiStore) { query = from t in query - join sm in _storeMappingRepository.Table - on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm - from sm in t_sm.DefaultIfEmpty() - where !t.LimitedToStores || storeId == sm.StoreId + join m in _storeMappingRepository.Table + on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = m.EntityId, c2 = m.EntityName } into tm + from m in tm.DefaultIfEmpty() + where !t.LimitedToStores || storeId == m.StoreId select t; - // Only distinct items (group by ID) + joinApplied = true; + } + + // ACL (access control list) + if (!showHidden && !QuerySettings.IgnoreAcl) + { + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + + query = from c in query + join a in _aclRepository.Table + on new { c1 = c.Id, c2 = entityName } equals new { c1 = a.EntityId, c2 = a.EntityName } into ca + from a in ca.DefaultIfEmpty() + where !c.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) + select c; + + joinApplied = true; + } + + if (joinApplied) + { + // Only distinct categories (group by ID) query = from t in query group t by t.Id into tGroup orderby tGroup.Key @@ -80,7 +124,7 @@ orderby tGroup.Key query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); - return query.ToListCached("db.topic.all-" + storeId); + return query; } public virtual void InsertTopic(Topic topic) diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index dc8b84bdc8..c369c9c9bc 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -13,9 +13,11 @@ + + diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs index 0c44f89540..30fcf453b6 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs @@ -79,8 +79,16 @@ public ActionResult PaymentMethod() [HttpPost] public ActionResult PaymentMethod(FormCollection form) { + // Display biling address on confirm page. _apiService.GetBillingAddress(); + var customer = Services.WorkContext.CurrentCustomer; + if (customer.BillingAddress == null) + { + NotifyError(T("Plugins.Payments.AmazonPay.MissingBillingAddress")); + return RedirectToAction("Cart", "ShoppingCart", new { area = "" }); + } + return RedirectToAction("Confirm", "Checkout", new { area = "" }); } diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs index d4515bf3e2..bce38ebbd0 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs @@ -59,17 +59,35 @@ public ActionResult Configure(AmazonPaySettings settings) return View(model); } - [HttpPost, AdminAuthorize, SaveSetting] - public ActionResult Configure(AmazonPaySettings settings, ConfigurationModel model, FormCollection form) + [HttpPost, AdminAuthorize] + public ActionResult Configure(ConfigurationModel model, FormCollection form) { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + if (!ModelState.IsValid) return Configure(settings); ModelState.Clear(); + + model.AccessKey = model.AccessKey.TrimSafe(); + model.ClientId = model.ClientId.TrimSafe(); + model.SecretKey = model.SecretKey.TrimSafe(); + model.SellerId = model.SellerId.TrimSafe(); + MiniMapper.Map(model, settings); - Services.Settings.SaveSetting(settings, x => x.DataFetching, 0, false); - Services.Settings.SaveSetting(settings, x => x.PollingMaxOrderCreationDays, 0, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } + + using (Services.Settings.BeginScope()) + { + Services.Settings.SaveSetting(settings, x => x.DataFetching, 0, false); + Services.Settings.SaveSetting(settings, x => x.PollingMaxOrderCreationDays, 0, false); + } var task = _scheduleTaskService.Value.GetTaskByType(); if (task != null) @@ -81,7 +99,7 @@ public ActionResult Configure(AmazonPaySettings settings, ConfigurationModel mod NotifySuccess(T("Plugins.Payments.AmazonPay.ConfigSaveNote")); - return Configure(settings); + return RedirectToConfiguration(AmazonPayPlugin.SystemName); } [HttpPost, AdminAuthorize] @@ -99,7 +117,7 @@ public ActionResult SaveAccessData(string accessData) NotifyError(exception.Message); } - return RedirectToAction("ConfigurePlugin", "Plugin", new { area = "admin", systemName = AmazonPayPlugin.SystemName }); + return RedirectToConfiguration(AmazonPayPlugin.SystemName); } [ValidateInput(false)] @@ -146,15 +164,16 @@ public ActionResult AuthenticationPublicInfo() public ActionResult AuthenticationButtonHandler() { + var returnUrl = Session["AmazonAuthReturnUrl"] as string; + var processor = _openAuthenticationService.Value.LoadExternalAuthenticationMethodBySystemName(AmazonPayPlugin.SystemName, Services.StoreContext.CurrentStore.Id); if (processor == null || !processor.IsMethodActive(_externalAuthenticationSettings.Value)) { - throw new SmartException(T("Plugins.Payments.AmazonPay.AuthenticationNotActive")); + NotifyError(T("Plugins.Payments.AmazonPay.AuthenticationNotActive")); + return new RedirectResult(Url.LogOn(returnUrl)); } - var returnUrl = Session["AmazonAuthReturnUrl"] as string; var result = _apiService.Authorize(returnUrl); - switch (result.AuthenticationStatus) { case OpenAuthenticationStatus.Error: diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 642b86adcc..900e79594d 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Login and Pay with Amazon SystemName: SmartStore.AmazonPay Group: Payment -Version: 3.0.3.2 -MinAppVersion: 3.0.0 +Version: 3.1.5.1 +MinAppVersion: 3.1.5 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.AmazonPay.dll diff --git a/src/Plugins/SmartStore.AmazonPay/Events/EventConsumer.cs b/src/Plugins/SmartStore.AmazonPay/Events/EventConsumer.cs new file mode 100644 index 0000000000..a9e120bd2f --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Events/EventConsumer.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using SmartStore.AmazonPay.Services; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Events; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Messages; +using SmartStore.Services.Orders; +using SmartStore.Web.Framework; + +namespace SmartStore.AmazonPay.Events +{ + public class EventConsumer : IConsumer, IConsumer + { + private readonly IPluginFinder _pluginFinder; + private readonly ICommonServices _services; + private readonly IOrderService _orderService; + private readonly Lazy _amazonPayService; + + public EventConsumer( + IPluginFinder pluginFinder, + ICommonServices services, + IOrderService orderService, + Lazy amazonPayService) + { + _pluginFinder = pluginFinder; + _services = services; + _orderService = orderService; + _amazonPayService = amazonPayService; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void HandleEvent(MessageModelCreatedEvent message) + { + if (message.MessageContext.MessageTemplate.Name != MessageTemplateNames.OrderPlacedCustomer) + return; + + var storeId = _services.StoreContext.CurrentStore.Id; + + if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayPlugin.SystemName, storeId)) + return; + + dynamic model = message.Model; + + if (model.Order == null) + return; + + var orderId = model.Order.ID; + + if (orderId is int id) + { + var order = _orderService.GetOrderById(id); + + var isAmazonPayment = (order != null && order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayPlugin.SystemName)); + var tokenValue = (isAmazonPayment ? _services.Localization.GetResource("Plugins.Payments.AmazonPay.BillingAddressMessageNote") : ""); + + model.AmazonPay = new Dictionary + { + { "BillingAddressMessageNote", tokenValue } + }; + } + } + + public void HandleEvent(OrderPaidEvent eventMessage) + { + if (eventMessage?.Order == null) + return; + + if (!eventMessage.Order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayPlugin.SystemName)) + return; + + if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayPlugin.SystemName, eventMessage.Order.StoreId)) + return; + + try + { + var settings = _services.Settings.LoadSetting(eventMessage.Order.StoreId); + + _amazonPayService.Value.CloseOrderReference(settings, eventMessage.Order); + } + catch (Exception exception) + { + Logger.Error(exception); + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs b/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs deleted file mode 100644 index 915c992002..0000000000 --- a/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using SmartStore.Core.Domain.Messages; -using SmartStore.Core.Events; -using SmartStore.Core.Plugins; -using SmartStore.Services; -using SmartStore.Services.Messages; -using SmartStore.Services.Orders; -using SmartStore.Web.Framework; - -namespace SmartStore.AmazonPay.Events -{ - public class MessageTokenEventConsumer : IConsumer - { - private readonly IPluginFinder _pluginFinder; - private readonly ICommonServices _services; - private readonly IOrderService _orderService; - - public MessageTokenEventConsumer( - IPluginFinder pluginFinder, - ICommonServices services, - IOrderService orderService) - { - _pluginFinder = pluginFinder; - _services = services; - _orderService = orderService; - } - - public void HandleEvent(MessageModelCreatedEvent message) - { - if (message.MessageContext.MessageTemplate.Name != MessageTemplateNames.OrderPlacedCustomer) - return; - - var storeId = _services.StoreContext.CurrentStore.Id; - - if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayPlugin.SystemName, storeId)) - return; - - dynamic model = message.Model; - - if (model.Order == null) - return; - - var orderId = model.Order.ID; - - if (orderId is int id) - { - var order = _orderService.GetOrderById(id); - - var isAmazonPayment = (order != null && order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayPlugin.SystemName)); - var tokenValue = (isAmazonPayment ? _services.Localization.GetResource("Plugins.Payments.AmazonPay.BillingAddressMessageNote") : ""); - - model.AmazonPay = new Dictionary - { - { "BillingAddressMessageNote", tokenValue } - }; - } - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml index b972fb8c6e..3ec171cd79 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml @@ -2,37 +2,38 @@ Amazon Pay - - Amazon Pay - + + Amazon Pay + - So richten Sie Login und Bezahlen mit Amazon ein:

    + So richten Sie Login und Bezahlen mit Amazon ein:
      +
    • Legen Sie in Ihrem Amazon Seller Central Konto eine Login mit Amazon Anwendung an. Verwenden Sie dabei die unten aufgeführten Zulässige JavaScript-Ursprünge und Zulässige Rückleitungs-URLs (SSL zwingend erforderlich!).
    • Tragen Sie Ihre Amazon-Zugangsdaten unten in die dafür vorgesehenen Felder ein. Die Zugangsdaten finden Sie in Ihrem Amazon Seller Central Konto oben links unter Integration > MWS Access Key.
    • Falls Sie Sofortbenachrichtigungen (IPN) erhalten möchten (SSL zwingend erforderlich!), so tragen Sie die unten aufgeführte IPN URL unter Einstellungen > Integrationseinstellungen > Sofortbenachrichtigungs-Einstellungen > Händler-URL ein.
    -

    Bitte fügen Sie Informationen zu Login und Bezahlen mit Amazon auf Ihrer Seite der Zahlungsarten ein (siehe CMS > Seiten). Bildmaterial finden Sie hier. -Textvorschläge:

      +
      Bitte fügen Sie Informationen zu Login und Bezahlen mit Amazon auf Ihrer Seite der Zahlungsarten ein (siehe CMS > Seiten). Bildmaterial finden Sie hier. +Textvorschläge:
      • Option 1: Amazon Pay: Zahlen Sie jetzt mit den Zahl- und Lieferinformationen aus Ihrem Amazon-Konto.
      • Option 2: Sie sind Amazon-Kunde? Zahlen Sie jetzt mit den Zahl- und Lieferinformationen aus Ihrem Amazon-Konto.
      • Option 3: Sie sind Amazon-Kunde? Zahlen Sie jetzt mit den Daten aus Ihrem Amazon-Konto.
      • -

      ]]> +
    ]]> - Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! - + Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! + Es wurde keine Auftrags-Referenz-ID durch Amazon übermittelt. - - Es wurde keine Access-Token durch Amazon übermittelt. - - - Unvollständige Amazon Profildaten! - + + Es wurde keine Access-Token durch Amazon übermittelt. + + + Unvollständige Amazon Profildaten! + Die Zahlungsart "Login und Bezahlen mit Amazon" ist für Shop "{0}" nicht verfügbar. @@ -59,54 +60,54 @@ Textvorschläge:
      Die Einstellungen wurden erfolgreich gespeichert. Starten Sie die Anwendung bitte neu, falls "Aktualisierung des Zahlungsstatus" geändert wurde. - - Amazon Authentifizierung ist nicht aktiv! - - - Ihre Zahlung mit Amazon Pay ist derzeit noch in Prüfung. Bitte beachten Sie, dass wir uns mit Ihnen in Kürze per Email in Verbindung setzen werden, falls noch Unklarheiten bestehen sollten. - - - Jetzt registrieren - + + Amazon Authentifizierung ist nicht aktiv! + + + Ihre Zahlung mit Amazon Pay ist derzeit noch in Prüfung. Bitte beachten Sie, dass wir uns mit Ihnen in Kürze per E-Mail in Verbindung setzen werden, falls noch Unklarheiten bestehen sollten. + + + Jetzt registrieren + Jetzt registrieren, falls Sie noch über keine Zugangsdaten verfügen. Sie erhalten durch Amazon Pay einen Satz neuer Zugangsdaten, die Sie bitte im Dialog Zugangsdaten einfügen eintragen.]]> - Zugangsdaten speichern - - - Zugangsdaten einfügen - - - Fügen Sie hier Ihre Zugangsdaten ein (merchant_id, access_key, secret_key, client_id etc.)... - - - Der Access-Token fehlt! - - - Amazon Datenabruf - - - Die Zugangsdaten wurden erfolgreich gespeichert. - - - Der Payload Parameter fehlt. - - - Eine Verschlüsselung von Zugangsdaten wird nicnt unterstützt. - - - Die Amazon Rechnungsanschrift des Kunden fehlt. - - + Zugangsdaten speichern + + + Zugangsdaten einfügen + + + Fügen Sie hier Ihre Zugangsdaten ein (merchant_id, access_key, secret_key, client_id etc.)... + + + Der Access-Token fehlt! + + + Amazon Datenabruf + + + Die Zugangsdaten wurden erfolgreich gespeichert. + + + Der Payload Parameter fehlt. + + + Eine Verschlüsselung von Zugangsdaten wird nicnt unterstützt. + + + Die Amazon Rechnungsanschrift fehlt. Bitte hinterlegen Sie eine Rechnungsanschrift bei Amazon oder wählen Sie eine andere Zahlart. + + Daten von Amazon wurden verarbeitet - - Zugangsdaten;Datenaustausch;Layout;Verschiedenes - - + + Zugangsdaten;Datenaustausch;Layout;Verschiedenes + + Mitteilungstyp;Mitteilungs ID;Autorisierungs ID;Buchungs ID;Rückerstatattungs ID;Referenz ID;Status;Statusaktualisierung;Gebühr;Autorisierungsbetrag;Buchungsbetrag;Erstattungsbetrag;Sofort buchen;Erstellt am;Verfällt am @@ -176,7 +177,7 @@ Textvorschläge:
        Bitte geben Sie diese URLs bei Amazon Seller Central unter Login mit Amazon ein. Das Protokoll der Rückleitungs-URL muss HTTPS sein. - + Farbe des Zahlungs-Button @@ -188,18 +189,18 @@ Textvorschläge:
          Hellgrau - - Dunkelgrau - + + Dunkelgrau + Größe des Zahlungs-Button Legt die Größe Zahlungs-Button (Pay-Button) fest. - - Klein - + + Klein + Medium @@ -209,30 +210,30 @@ Textvorschläge:
            Extra-Groß - - Art des Anmelde-Button - - - Legt die bevorzugte Art des Anmelde-Button (Loogin-Button) fest. - - - Login - - - Login mit Amazon - - - Farbe des Anmelde-Button - - - Legt die bevorzugte Farbe des Anmelde-Button (Login-Button) fest. - - - Größe des Anmelde-Button - - - Legt die Größe Anmelde-Button (Login-Button) fest. - + + Art des Anmelde-Button + + + Legt die bevorzugte Art des Anmelde-Button (Loogin-Button) fest. + + + Login + + + Login mit Amazon + + + Farbe des Anmelde-Button + + + Legt die bevorzugte Farbe des Anmelde-Button (Login-Button) fest. + + + Größe des Anmelde-Button + + + Legt die Größe Anmelde-Button (Login-Button) fest. + Aktualisierung des Zahlungsstatus @@ -240,7 +241,9 @@ Textvorschläge:
              Legt die Methode fest, mit deren Hilfe der Zahlungsstatus aktualisiert werden soll. - Die URL für IPN (Sofortbenachrichtigungen) muss in Amazon Seller Central separat für Sandbox und Live-Modus eingetragen werden. Für den Live-Modus ist ein gültiges, von einer vertrauenswürdigen Zertifizierungsstelle ausgegebenes SSL-Zertifikat erforderlich, selbstsignierte Zertifikate sind nicht zulässig. Für die Sandbox ist kein SSL-Zertifikat erforderlich. Die IPN-URL muss in diesem Fall mit http angegeben werden. + + + IPN (Instant Payment Notification) @@ -252,7 +255,7 @@ Textvorschläge:
                IPN URL - Bitte geben Sie diese URL bei Amazon Seller Central unter Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein. + Bitte geben Sie diese URL bei Amazon Seller Central unter Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein. Zahlungsaktion @@ -267,7 +270,9 @@ Textvorschläge:
                  Autorisierung sofort, Abbuchung später - Bitte benutzen Sie "Sofort abbuchen" nur, wenn Sie Ware am selben Tag der Bestellung verschicken und Sie für diesen Dienst zugelassen sind. Aktivieren Sie diese Option bitte erst nach Rücksprache mit Amazon Pay. + + Sofort abbuchen nur, wenn Sie Ware am selben Tag der Bestellung verschicken und Sie für diesen Dienst zugelassen sind. Aktivieren Sie diese Option bitte erst nach Rücksprache mit Amazon Pay.]]> + Autorisierungsmethode @@ -302,18 +307,6 @@ Textvorschläge:
                    Legt fest, ob der Amazon Pay Button auch im Miniwarenkorb angezeigt werden soll. - - Zusätzliche Gebühren - - - Zusätzliche Gebühren, die dem Kunden für die Inanspruchnahme des Dienstes berechnet werden sollen. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. - Auftragsnotizen anlegen @@ -330,12 +323,12 @@ Textvorschläge:
                      Über Ablehnung einer Autorisierung informieren - Legt fest, dass Auftragsnotizen im Fall einer Ablehnung einer Autorisierung durch Amazon angelegt werden, die auch für den Kunden einsehbar sind. Zusätzlich wir der Kunde per Email über den Sachverhalt informiert. + Legt fest, dass Auftragsnotizen im Fall einer Ablehnung einer Autorisierung durch Amazon angelegt werden, die auch für den Kunden einsehbar sind. Zusätzlich wird der Kunde per E-Mail über den Sachverhalt informiert. Fehlermeldung anhängen - Legt fest, ob der genaue Wortlaut der Fehlermeldung der Auftragsnotiz bzw. Email angehängt werden soll. + Legt fest, ob der genaue Wortlaut der Fehlermeldung der Auftragsnotiz bzw. E-Mail angehängt werden soll. \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml index 37e8e2f926..bc510fac8e 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml @@ -2,38 +2,39 @@ Amazon Pay - - Amazon Pay - + + Amazon Pay + - How to set up Login and Pay with Amazon:

                        + How to set up Login and Pay with Amazon:
                          +
                        • Create a Login with Amazon application in your Amazon Seller Central account. For this, use Allowed JavaScript Origins and Allowed Return URLs listed below (SSL required!).
                        • Enter your Amazon credentials in the fields provided below. You can find these credentials in your Amazon Seller Central account at Integration > MWS Access Key.
                        • If you would like to receive instant payment notifications (SSL required!) enter the IPN URL listed bewlow under Settings > Integration Settings > Instant Notification Settings > Merchant URL.
                        -

                        Please add information about Login and Pay with Amazon on your payment page (see CMS > Topics). You will find picture material here. -Text suggestions:

                          +
                          Please add information about Login and Pay with Amazon on your payment page (see CMS > Topics). You will find picture material here. +Text suggestions:
                          • Option 1: Amazon Pay: Pay now with the payment and shipping information from your Amazon account.
                          • Option 2: Already Amazon customer? Pay now with the payment and shipping information from your Amazon account.
                          • Option 3: Already Amazon customer? Pay now with the data from your Amazon account.
                          • -

                          ]]> +
                        ]]> - - Please note that this billing address is possibly not the valid billing address for this order! - + + Please note that this billing address is possibly not the valid billing address for this order! + There was no order reference ID transmitted by Amazon. - - There was no access token transmitted by Amazon. - - - Incomplete Amazon profile data! - - + + There was no access token transmitted by Amazon. + + + Incomplete Amazon profile data! + + Payment method "Login and Pay with Amazon" is not available for store "{0}". @@ -59,54 +60,54 @@ Text suggestions:
                          The settings were successfully saved. Please restart the application if "Updating the payment status" has been changed. - - Amazon authentication is not active! - - - Your transaction with Amazon Pay is currently being validated. Please be aware that we will inform you shortly as needed. - - - Register now - + + Amazon authentication is not active! + + + Your transaction with Amazon Pay is currently being validated. Please be aware that we will inform you shortly as needed. + + + Register now + Register now if you don't have an account yet. Amazon Pay will provide you with a set of new access data, which you can enter in the Paste access data dialog.]]> - - Save access data - - - Paste access data - - - Paste here your access data (merchant_id, access_key, secret_key, client_id etc.)... - - - Missing access token! - - - Amazon data polling - - - The access data has been saved successfully. - - - The payload parameter is missing. - - - Encryption of access data is not supported. - - - Missing Amazon billing address of the customer. - - - Data from Amazon has been processed - - - Access data;Data exchange;Layout;Miscellaneous - - + + Save access data + + + Paste access data + + + Paste here your access data (merchant_id, access_key, secret_key, client_id etc.)... + + + Missing access token! + + + Amazon data polling + + + The access data has been saved successfully. + + + The payload parameter is missing. + + + Encryption of access data is not supported. + + + Missing Amazon billing address of the customer. Please enter a billing address at Amazon or choose another payment method. + + + Data from Amazon has been processed + + + Access data;Data exchange;Layout;Miscellaneous + + Message type;Message ID;Authorization ID;Capture ID;Refund ID;Reference ID;State;State update;Fee;Authorized amount;Captured amount;Refunded amount;Capture now;Creation;Expiration @@ -145,7 +146,7 @@ Text suggestions:
                            Japan - + Your Access Key ID @@ -188,18 +189,18 @@ Text suggestions:
                              Light gray - - Dark gray - + + Dark gray + Pay button size Specifies the size of the Pay button. - - Small - + + Small + Medium @@ -209,30 +210,30 @@ Text suggestions:
                                Extra large - - Login button type - - - Specifies the prefered type of the Login button. - - - Login - - - Login with Amazon - - - Login button color - - - Specifies the prefered color of the Login button. - - - Login button size - - - Specifies the size of the Login button. - + + Login button type + + + Specifies the prefered type of the Login button. + + + Login + + + Login with Amazon + + + Login button color + + + Specifies the prefered color of the Login button. + + + Login button size + + + Specifies the size of the Login button. + Updating the payment status @@ -240,7 +241,9 @@ Text suggestions:
                                  Specifies the method used to update the payment status. - The URL for IPN (Instant Payment Notification) must be entered separately for Sandbox and Live Mode in Amazon Seller Central. Live mode requires a valid SSL certificate issued by a trusted certificate authority, self-signed certificates are not permitted. The sandbox does not require an SSL certificate. In this case, the IPN URL must be specified with http. + + + IPN (Instant Payment Notification) @@ -253,7 +256,7 @@ Text suggestions:
                                    Please enter this URL at Amazon Seller Central under Integration Settings - Instant Notification Settings - Merchant URL. - + Payment action @@ -267,7 +270,9 @@ Text suggestions:
                                      Authorize immediately, debit later - Please use "Immediately debit" method only in the case you are shipping goods on the same day they are ordered and you have been white-listed for this service. Do not activate this option without contacting Amazon Pay first. + + Immediately debit method only in the case you are shipping goods on the same day they are ordered and you have been white-listed for this service. Do not activate this option without contacting Amazon Pay first.]]> + Authorize method @@ -302,18 +307,6 @@ Text suggestions:
                                        Specifies to show the Amazon Pay button in the mini shopping cart too. - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee percentage - - - Specifies whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. - Create order notes diff --git a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs index 6be828b2f5..cf304df151 100644 --- a/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.AmazonPay/Models/ConfigurationModel.cs @@ -59,10 +59,10 @@ public class ConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.AmazonPay.ShowButtonInMiniShoppingCart")] public bool ShowButtonInMiniShoppingCart { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AdditionalFee")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AdditionalFeePercentage")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } [SmartResourceDisplayName("Plugins.Payments.AmazonPay.AddOrderNotes")] diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs index b643ae9d01..4b7ef18f9d 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayService.cs @@ -144,7 +144,8 @@ public void SetupConfiguration(ConfigurationModel model) model.KeyShareUrl = GetPluginUrl("ShareKey", store.SslEnabled); model.LanguageLocale = language.UniqueSeoCode.ToAmazonLanguageCode('_'); model.MerchantStoreDescription = store.Name.Truncate(2048); - model.MerchantPrivacyNoticeUrl = urlHelper.RouteUrl("Topic", new { SystemName = "privacyinfo" }, store.SslEnabled ? "https" : "http"); + + model.MerchantPrivacyNoticeUrl = urlHelper.RouteUrl("Topic", new { SeName = urlHelper.TopicSeName("privacyinfo") }, store.SslEnabled ? "https" : "http"); model.MerchantSandboxIpnUrl = model.IpnUrl; model.MerchantProductionIpnUrl = model.IpnUrl; @@ -155,12 +156,14 @@ public void SetupConfiguration(ConfigurationModel model) foreach (var entity in allStores) { - if (entity.SecureUrl.HasValue()) + // SSL required! + var shopUrl = entity.SslEnabled ? entity.SecureUrl : entity.Url; + if (shopUrl.HasValue()) { try { - var uri = new Uri(entity.SecureUrl); // Only protocol and domain name. + var uri = new Uri(shopUrl); var loginDomain = uri.GetLeftPart(UriPartial.Scheme | UriPartial.Authority).EmptyNull().TrimEnd('/'); model.MerchantLoginDomains.Add(loginDomain); @@ -171,9 +174,8 @@ public void SetupConfiguration(ConfigurationModel model) } catch { } - var urlRoot = entity.SecureUrl.EnsureEndsWith("/"); - var payHandlerUrl = urlRoot + "Plugins/SmartStore.AmazonPay/AmazonPayShoppingCart/PayButtonHandler"; - var authHandlerUrl = urlRoot + "Plugins/SmartStore.AmazonPay/AmazonPay/AuthenticationButtonHandler"; + var payHandlerUrl = shopUrl.EnsureEndsWith("/") + "Plugins/SmartStore.AmazonPay/AmazonPayShoppingCart/PayButtonHandler"; + var authHandlerUrl = shopUrl.EnsureEndsWith("/") + "Plugins/SmartStore.AmazonPay/AmazonPay/AuthenticationButtonHandler"; model.MerchantLoginRedirectUrls.Add(payHandlerUrl); model.MerchantLoginRedirectUrls.Add(authHandlerUrl); @@ -191,7 +193,7 @@ public void SetupConfiguration(ConfigurationModel model) var merchantCountry = _countryService.GetCountryById(_companyInformationSettings.CountryId); if (merchantCountry != null) { - model.MerchantCountry = merchantCountry.GetLocalized(x => x.Name, language.Id, false, false); + model.MerchantCountry = merchantCountry.GetLocalized(x => x.Name, language, false, false); } } @@ -611,20 +613,6 @@ private void ProcessCaptureResult(Client client, AmazonPaySettings settings, Ord if (data.State.IsCaseInsensitiveEqual("Completed") && _orderProcessingService.CanMarkOrderAsPaid(order)) { _orderProcessingService.MarkOrderAsPaid(order); - - // You can still perform captures against any open authorizations, but you cannot create any new authorizations on the - // Order Reference object. You can still execute refunds against the Order Reference object. - var orderAttribute = DeserializeOrderAttribute(order); - - var closeRequest = new CloseOrderReferenceRequest() - .WithMerchantId(settings.SellerId) - .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); - - var closeResponse = client.CloseOrderReference(closeRequest); - if (!closeResponse.GetSuccess()) - { - LogError(closeResponse, true); - } } else if (data.State.IsCaseInsensitiveEqual("Declined") && _orderProcessingService.CanVoidOffline(order)) { @@ -784,6 +772,31 @@ private void EarlyPolling(int orderId, AmazonPaySettings settings) }); } + public void CloseOrderReference(AmazonPaySettings settings, Order order) + { + // You can still perform captures against any open authorizations, but you cannot create any new authorizations on the + // Order Reference object. You can still execute refunds against the Order Reference object. + var orderAttribute = DeserializeOrderAttribute(order); + if (!orderAttribute.OrderReferenceClosed && orderAttribute.OrderReferenceId.HasValue()) + { + var client = CreateClient(settings); + var closeRequest = new CloseOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); + + var closeResponse = client.CloseOrderReference(closeRequest); + if (closeResponse.GetSuccess()) + { + orderAttribute.OrderReferenceClosed = true; + SerializeOrderAttribute(orderAttribute, order); + } + else + { + LogError(closeResponse, true); + } + } + } + public void AddCustomerOrderNoteLoop(AmazonPayActionState state) { try @@ -871,7 +884,7 @@ public void GetBillingAddress() // We must ignore countryAllowsBilling because the customer cannot choose another billing address in Amazon checkout. //if (!countryAllowsBilling) - // return false; + // return; var existingAddress = customer.Addresses.ToList().FindAddress(address, true); if (existingAddress == null) @@ -902,7 +915,7 @@ public void GetBillingAddress() } else { - Logger.Error(new Exception(getOrderResponse.GetJson()), T("Plugins.Payments.AmazonPay.MissingBillingAddress")); + // No billing address at Amazon? We cannot proceed. } } else @@ -975,7 +988,9 @@ public PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest request) { // Must be redirected to checkout payment page. _httpContext.Session["AmazonPayFailedPaymentReason"] = id; - _httpContext.Response.RedirectToRoute(new { Controller = "Checkout", Action = "PaymentMethod", Area = "" }); + + var urlHelper = new UrlHelper(_httpContext.Request.RequestContext); + _httpContext.Response.Redirect(urlHelper.Action("PaymentMethod", "Checkout", new { area = "" })); } } } @@ -991,12 +1006,6 @@ public PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest request) return result; } } - - var confirmRequest = new ConfirmOrderReferenceRequest() - .WithMerchantId(settings.SellerId) - .WithAmazonOrderReferenceId(state.OrderReferenceId); - - client.ConfirmOrderReference(confirmRequest); } catch (Exception exception) { @@ -1033,6 +1042,13 @@ public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest request) informCustomerAboutErrors = settings.InformCustomerAboutErrors; informCustomerAddErrors = settings.InformCustomerAddErrors; + // Confirm order. This already generates the payment object at Amazon. + var confirmRequest = new ConfirmOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(state.OrderReferenceId); + + client.ConfirmOrderReference(confirmRequest); + // Authorize. if (settings.AuthorizeMethod == AmazonPayAuthorizeMethod.Omnichronous) { @@ -1111,7 +1127,9 @@ public ProcessPaymentResult ProcessPayment(ProcessPaymentRequest request) { // Must be redirected to checkout payment page. _httpContext.Session["AmazonPayFailedPaymentReason"] = reason; - _httpContext.Response.RedirectToRoute(new { Controller = "Checkout", Action = "PaymentMethod", Area = "" }); + + var urlHelper = new UrlHelper(_httpContext.Request.RequestContext); + _httpContext.Response.Redirect(urlHelper.Action("PaymentMethod", "Checkout", new { area = "" })); } } } @@ -1187,12 +1205,30 @@ public void PostProcessPayment(PostProcessPaymentRequest request) try { var state = _httpContext.GetAmazonPayState(_services.Localization); - var orderAttribute = new AmazonPayOrderAttribute { OrderReferenceId = state.OrderReferenceId }; + if (request.Order.PaymentStatus == PaymentStatus.Paid) + { + var settings = _services.Settings.LoadSetting(request.Order.StoreId); + var client = CreateClient(settings); + var closeRequest = new CloseOrderReferenceRequest() + .WithMerchantId(settings.SellerId) + .WithAmazonOrderReferenceId(orderAttribute.OrderReferenceId); + + var closeResponse = client.CloseOrderReference(closeRequest); + if (closeResponse.GetSuccess()) + { + orderAttribute.OrderReferenceClosed = true; + } + else + { + LogError(closeResponse, true); + } + } + SerializeOrderAttribute(orderAttribute, request.Order); } catch (Exception exception) @@ -1203,7 +1239,7 @@ public void PostProcessPayment(PostProcessPaymentRequest request) public CapturePaymentResult Capture(CapturePaymentRequest request) { - var result = new CapturePaymentResult() + var result = new CapturePaymentResult { NewPaymentStatus = request.Order.PaymentStatus }; @@ -1360,9 +1396,9 @@ public VoidPaymentResult Void(VoidPaymentRequest request) public void ProcessIpn(HttpRequestBase request) { + string json = null; try { - string json = null; using (var reader = new StreamReader(request.InputStream)) { json = reader.ReadToEnd(); @@ -1470,7 +1506,7 @@ public void ProcessIpn(HttpRequestBase request) } catch (Exception exception) { - Logger.Error(exception); + Logger.Error(exception, json); } } diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs index e612df6b22..11e22c003b 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayServiceHelper.cs @@ -115,10 +115,10 @@ private void AddOrderNote(AmazonPaySettings settings, Order order, string anyStr return; var sb = new StringBuilder(); - var faviconUrl = "{0}Plugins/{1}/Content/images/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(false), AmazonPayPlugin.SystemName); + var faviconUrl = "{0}Plugins/{1}/Content/images/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(), AmazonPayPlugin.SystemName); - sb.AppendFormat("", faviconUrl); - sb.AppendFormat("{0}", T("Plugins.Payments.AmazonPay.AmazonDataProcessed")); + sb.AppendFormat("", faviconUrl); + sb.Append(T("Plugins.Payments.AmazonPay.AmazonDataProcessed")); sb.Append(":
                                        "); sb.Append(anyString); @@ -425,6 +425,7 @@ private AmazonPayData GetDetails(RefundResponse response) data.ReferenceId = response.GetRefundReferenceId(); data.Creation = response.GetCreationTimestamp(); data.Fee = new AmazonPayPrice(response.GetRefundFee(), response.GetRefundFeeCurrencyCode()); + data.RefundId = response.GetAmazonRefundId(); data.RefundedAmount = new AmazonPayPrice(response.GetRefundAmount(), response.GetRefundAmountCurrencyCode()); data.ReasonCode = response.GetReasonCode(); data.ReasonDescription = response.GetReasonDescription(); diff --git a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs index b5316fc229..7d54a8456b 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/AmazonPayUtilities.cs @@ -24,6 +24,7 @@ public class AmazonPayActionState public class AmazonPayOrderAttribute { public string OrderReferenceId { get; set; } + public bool OrderReferenceClosed { get; set; } } diff --git a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs index baa64192a8..6f3f727d48 100644 --- a/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs +++ b/src/Plugins/SmartStore.AmazonPay/Services/IAmazonPayService.cs @@ -1,6 +1,7 @@ using System.Web; using System.Web.Mvc; using SmartStore.AmazonPay.Models; +using SmartStore.Core.Domain.Orders; using SmartStore.Services.Authentication.External; using SmartStore.Services.Payments; @@ -12,6 +13,8 @@ public partial interface IAmazonPayService : IExternalProviderAuthorizer AmazonPayViewModel CreateViewModel(AmazonPayRequestType type, TempDataDictionary tempData); + void CloseOrderReference(AmazonPaySettings settings, Order order); + void AddCustomerOrderNoteLoop(AmazonPayActionState state); void GetBillingAddress(); diff --git a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj index fc7185bef3..4f7473a224 100644 --- a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj +++ b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj @@ -151,7 +151,7 @@ - + diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml index 8a714d95e5..213e0c1cf5 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml @@ -1,13 +1,13 @@ -@using SmartStore.Web.Framework; +@model ConfigurationModel +@using SmartStore.Web.Framework; @using SmartStore.AmazonPay.Models; @using SmartStore.AmazonPay; -@model ConfigurationModel @{ Layout = ""; }
                                        - @@ -41,13 +41,6 @@
                                        - - -
                                        - @T("Plugins.Payments.AmazonPay.RegisterNote") -
                                        - -   @@ -63,6 +56,14 @@ + + + +
                                        + @Html.Raw(T("Plugins.Payments.AmazonPay.RegisterNote")) +
                                        + + @Html.SmartLabelFor(model => model.UseSandbox) @@ -86,8 +87,7 @@ @Html.SmartLabelFor(model => model.AccessKey) - @Html.SettingOverrideCheckbox(model => model.AccessKey) - @Html.TextBoxFor(model => model.AccessKey) + @Html.SettingEditorFor(model => model.AccessKey) @Html.ValidationMessageFor(model => model.AccessKey) @@ -105,8 +105,7 @@ @Html.SmartLabelFor(model => model.ClientId) - @Html.SettingOverrideCheckbox(model => model.ClientId) - @Html.TextBoxFor(model => model.ClientId) + @Html.SettingEditorFor(model => model.ClientId) @Html.ValidationMessageFor(model => model.ClientId) @@ -115,46 +114,44 @@ @Html.SmartLabelFor(model => model.Marketplace) - @Html.SettingOverrideCheckbox(model => model.Marketplace) - @Html.DropDownListFor(model => model.Marketplace, new List + @Html.SettingEditorFor(model => model.Marketplace, Html.DropDownListFor(model => model.Marketplace, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.Marketplace.De"), Value = "de" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.Marketplace.Uk"), Value = "uk" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.Marketplace.Us"), Value = "us" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.Marketplace.Jp"), Value = "jp" } - }) + })) @Html.ValidationMessageFor(model => model.Marketplace) - @if (Model.CurrentMerchantLoginDomains.Any()) - { - - - @Html.SmartLabelFor(model => model.CurrentMerchantLoginDomains) - - - @foreach (var item in Model.CurrentMerchantLoginDomains) + + + @Html.SmartLabelFor(model => model.CurrentMerchantLoginDomains) + + + @if (Model.CurrentMerchantLoginDomains.Any()) + { + foreach (var item in Model.CurrentMerchantLoginDomains) {
                                        @Html.Raw(item)
                                        } - - - } - @if (Model.CurrentMerchantLoginRedirectUrls.Any()) - { - - - @Html.SmartLabelFor(model => model.CurrentMerchantLoginRedirectUrls) - - - @foreach (var item in Model.CurrentMerchantLoginRedirectUrls) + } + + + + + @Html.SmartLabelFor(model => model.CurrentMerchantLoginRedirectUrls) + + + @if (Model.CurrentMerchantLoginRedirectUrls.Any()) + { + foreach (var item in Model.CurrentMerchantLoginRedirectUrls) {
                                        @Html.Raw(item)
                                        } - - - } - + } + +
                                        @@ -167,8 +164,7 @@ @Html.SmartLabelFor(model => model.AuthorizeMethod) - @Html.SettingOverrideCheckbox(model => model.AuthorizeMethod) - @Html.DropDownListFor(model => model.AuthorizeMethod, Model.AuthorizeMethods) + @Html.SettingEditorFor(model => model.AuthorizeMethod, Html.DropDownListFor(model => model.AuthorizeMethod, Model.AuthorizeMethods)) @Html.ValidationMessageFor(model => model.AuthorizeMethod) @@ -177,15 +173,15 @@ @Html.SmartLabelFor(model => model.TransactionType) - @Html.SettingOverrideCheckbox(model => model.TransactionType) - @Html.DropDownListFor(model => model.TransactionType, Model.TransactionTypes) + @Html.SettingEditorFor(model => model.TransactionType, Html.DropDownListFor(model => model.TransactionType, Model.TransactionTypes)) @Html.ValidationMessageFor(model => model.TransactionType) - + +
                                        - @T("Plugins.Payments.AmazonPay.TransactionType.Warning") + @Html.Raw(T("Plugins.Payments.AmazonPay.TransactionType.Warning"))
                                        @@ -194,8 +190,7 @@ @Html.SmartLabelFor(model => model.SaveEmailAndPhone) - @Html.SettingOverrideCheckbox(model => model.SaveEmailAndPhone) - @Html.DropDownListFor(model => model.SaveEmailAndPhone, Model.SaveEmailAndPhones) + @Html.SettingEditorFor(model => model.SaveEmailAndPhone, Html.DropDownListFor(model => model.SaveEmailAndPhone, Model.SaveEmailAndPhones)) @Html.ValidationMessageFor(model => model.SaveEmailAndPhone) @@ -213,8 +208,8 @@ @Html.SmartLabelFor(model => model.InformCustomerAboutErrors) - @Html.SettingOverrideCheckbox(model => model.InformCustomerAboutErrors) - @Html.CheckBoxFor(model => model.InformCustomerAboutErrors, new { data_toggler_for = "#InformCustomerAddErrorsContainer" }) + @Html.SettingEditorFor(model => model.InformCustomerAboutErrors, + Html.CheckBoxFor(model => model.InformCustomerAboutErrors, new { data_toggler_for = "#InformCustomerAddErrorsContainer" })) @Html.ValidationMessageFor(model => model.InformCustomerAboutErrors) @@ -254,9 +249,10 @@ - + +
                                        - @T("Plugins.Payments.AmazonPay.DataFetching.Warning") + @Html.Raw(T("Plugins.Payments.AmazonPay.DataFetching.Warning"))
                                        @@ -273,13 +269,12 @@ @Html.SmartLabelFor(model => model.PayButtonColor) - @Html.SettingOverrideCheckbox(model => model.PayButtonColor) - @Html.DropDownListFor(model => model.PayButtonColor, new List + @Html.SettingEditorFor(model => model.PayButtonColor, Html.DropDownListFor(model => model.PayButtonColor, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.Gold"), Value = "Gold" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.LightGray"), Value = "LightGray" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.DarkGray"), Value = "DarkGray" } - }) + })) @Html.ValidationMessageFor(model => model.PayButtonColor) @@ -288,14 +283,13 @@ @Html.SmartLabelFor(model => model.PayButtonSize) - @Html.SettingOverrideCheckbox(model => model.PayButtonSize) - @Html.DropDownListFor(model => model.PayButtonSize, new List + @Html.SettingEditorFor(model => model.PayButtonSize, Html.DropDownListFor(model => model.PayButtonSize, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Small"), Value = "small" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Medium"), Value = "medium" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Large"), Value = "large" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Xlarge"), Value = "x-large" } - }) + })) @Html.ValidationMessageFor(model => model.PayButtonSize) @@ -305,12 +299,11 @@ @Html.SmartLabelFor(model => model.AuthButtonType) - @Html.SettingOverrideCheckbox(model => model.AuthButtonType) - @Html.DropDownListFor(model => model.AuthButtonType, new List + @Html.SettingEditorFor(model => model.AuthButtonType, Html.DropDownListFor(model => model.AuthButtonType, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.AuthButtonType.Login"), Value = "Login" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.AuthButtonType.LwA"), Value = "LwA" } - }) + })) @Html.ValidationMessageFor(model => model.AuthButtonType) @@ -319,13 +312,12 @@ @Html.SmartLabelFor(model => model.AuthButtonColor) - @Html.SettingOverrideCheckbox(model => model.AuthButtonColor) - @Html.DropDownListFor(model => model.AuthButtonColor, new List + @Html.SettingEditorFor(model => model.AuthButtonColor, Html.DropDownListFor(model => model.AuthButtonColor, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.Gold"), Value = "Gold" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.LightGray"), Value = "LightGray" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonColor.DarkGray"), Value = "DarkGray" } - }) + })) @Html.ValidationMessageFor(model => model.AuthButtonColor) @@ -334,14 +326,13 @@ @Html.SmartLabelFor(model => model.AuthButtonSize) - @Html.SettingOverrideCheckbox(model => model.AuthButtonSize) - @Html.DropDownListFor(model => model.AuthButtonSize, new List + @Html.SettingEditorFor(model => model.AuthButtonSize, Html.DropDownListFor(model => model.AuthButtonSize, new List { new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Small"), Value = "small" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Medium"), Value = "medium" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Large"), Value = "large" }, new SelectListItem { Text = @T("Plugins.Payments.AmazonPay.ButtonSize.Xlarge"), Value = "x-large" } - }) + })) @Html.ValidationMessageFor(model => model.AuthButtonSize) @@ -376,8 +367,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) - @Html.SettingOverrideCheckbox(model => model.AdditionalFee) - @Html.EditorFor(model => model.AdditionalFee, new { postfix = Model.PrimaryStoreCurrencyCode }) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayShoppingCart/OrderReviewData.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayShoppingCart/OrderReviewData.cshtml index 9976623a39..bdea3a8e11 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayShoppingCart/OrderReviewData.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPayShoppingCart/OrderReviewData.cshtml @@ -5,7 +5,7 @@ Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.AmazonPay/Content/SmartStore.AmazonPay.css")); } -
                                        +
                                        diff --git a/src/Plugins/SmartStore.AmazonPay/Views/Shared/ScriptingLoginButton.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/Shared/ScriptingLoginButton.cshtml index 37caa5ad52..8d4d7430fe 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/Shared/ScriptingLoginButton.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/Shared/ScriptingLoginButton.cshtml @@ -1,59 +1,90 @@ @using SmartStore.AmazonPay.Services; @model SmartStore.AmazonPay.Models.AmazonPayViewModel - - - diff --git a/src/Plugins/SmartStore.AmazonPay/changelog.md b/src/Plugins/SmartStore.AmazonPay/changelog.md index 1fce63ded0..54ed36a390 100644 --- a/src/Plugins/SmartStore.AmazonPay/changelog.md +++ b/src/Plugins/SmartStore.AmazonPay/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Login and Pay with Amazon 3.1.5.1 +###Bugfixes +* Checkout attributes were always ignored + ##Login and Pay with Amazon 3.0.3.2 ###Improvements * Supports merchants registered in the USA and Japan diff --git a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs index cc103617be..d9451d5e75 100644 --- a/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs +++ b/src/Plugins/SmartStore.Clickatell/Controllers/SmsClickatellController.cs @@ -3,7 +3,6 @@ using SmartStore.Clickatell.Models; using SmartStore.ComponentModel; using SmartStore.Core.Plugins; -using SmartStore.Services; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; @@ -14,14 +13,10 @@ namespace SmartStore.Clickatell.Controllers [AdminAuthorize] public class SmsClickatellController : PluginControllerBase { - private readonly ICommonServices _services; - private readonly IPluginFinder _pluginFinder; + private readonly IPluginFinder _pluginFinder; - public SmsClickatellController( - ICommonServices services, - IPluginFinder pluginFinder) + public SmsClickatellController(IPluginFinder pluginFinder) { - _services = services; _pluginFinder = pluginFinder; } @@ -30,22 +25,27 @@ public ActionResult Configure(ClickatellSettings settings) { var model = new SmsClickatellModel(); MiniMapper.Map(settings, model); + return View(model); } [HttpPost, SaveSetting, FormValueRequired("save")] - public ActionResult Configure(ClickatellSettings settings, SmsClickatellModel model, FormCollection form) + public ActionResult Configure(ClickatellSettings settings, SmsClickatellModel model) { - if (ModelState.IsValid) + if (!ModelState.IsValid) { - MiniMapper.Map(model, settings); - NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + return Configure(settings); } - return Configure(settings); + MiniMapper.Map(model, settings); + settings.ApiId = model.ApiId.TrimSafe(); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return RedirectToConfiguration(ClickatellSmsProvider.SystemName); } - [HttpPost, ActionName("Configure"), FormValueRequired("test-sms")] + [HttpPost, ActionName("Configure"), FormValueRequired("test-sms")] public ActionResult TestSms(SmsClickatellModel model) { try @@ -57,7 +57,7 @@ public ActionResult TestSms(SmsClickatellModel model) } else { - var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName("SmartStore.Clickatell"); + var pluginDescriptor = _pluginFinder.GetPluginDescriptorBySystemName(ClickatellSmsProvider.SystemName); var plugin = pluginDescriptor.Instance() as ClickatellSmsProvider; plugin.SendSms(model.TestMessage); diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index db2a159b1b..fa7b60c219 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll ResourceRootKey: Plugins.Sms.Clickatell \ No newline at end of file diff --git a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml index e6fa35f71e..09992e2c48 100644 --- a/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml +++ b/src/Plugins/SmartStore.Clickatell/Views/SmsClickatell/Configure.cshtml @@ -75,20 +75,20 @@ @Html.ValidationMessageFor(model => model.TestMessage) - - -   - - - @if (Model.TestSmsResult.HasValue()) - { -
                                        + @if (Model.TestSmsResult.HasValue()) + { + + +   + + +
                                        @Model.TestSmsResult @Model.TestSmsDetailResult
                                        - } - - + + + }   diff --git a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs index a81c0d9e70..98c3b165e4 100644 --- a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs +++ b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs @@ -8,7 +8,6 @@ namespace SmartStore.DevTools.Controllers { - public class DevToolsController : SmartController { private readonly ICommonServices _services; @@ -27,7 +26,7 @@ public ActionResult Configure(ProfilerSettings settings) [SaveSetting(false), HttpPost, ChildActionOnly, ActionName("Configure")] public ActionResult ConfigurePost(ProfilerSettings settings) { - return Configure(settings); + return RedirectToConfiguration("SmartStore.DevTools"); } public ActionResult MiniProfiler() @@ -79,6 +78,11 @@ public ActionResult ProductEditTab(int productId, FormCollection form) var result = PartialView(model); result.ViewData.TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "CustomProperties[DevTools]" }; return result; - } - } + } + + public ActionResult MyDemoWidget() + { + return Content("Hello world! This is a sample widget created for demonstration purposes by Dev-Tools plugin."); + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index 20fc75a999..15391bbfbc 100644 --- a/src/Plugins/SmartStore.DevTools/Description.txt +++ b/src/Plugins/SmartStore.DevTools/Description.txt @@ -1,8 +1,8 @@ FriendlyName: SmartStore.NET Developer Tools (MiniProfiler and other goodies) SystemName: SmartStore.DevTools Group: Developer -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 1 FileName: SmartStore.DevTools.dll ResourceRootKey: Plugins.Developer.DevTools \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs b/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs index c63088dce6..6001b8b582 100644 --- a/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs +++ b/src/Plugins/SmartStore.DevTools/DevToolsPlugin.cs @@ -1,28 +1,50 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web.Routing; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Data; using SmartStore.Data.Setup; -using SmartStore.Services.Common; using SmartStore.Services.Configuration; +using SmartStore.Core.Caching; namespace SmartStore.DevTools { - public class DevToolsPlugin : BasePlugin, IConfigurable - { + [DisplayOrder(10)] + [SystemName("Widgets.DevToolsDemo")] + [FriendlyName("Dev-Tools Demo Widget")] + public class DevToolsPlugin : BasePlugin, IConfigurable //, IWidget + { private readonly ISettingService _settingService; + private readonly ICacheableRouteRegistrar _cacheableRouteRegistrar; - public DevToolsPlugin(ISettingService settingService) + public DevToolsPlugin(ISettingService settingService, + ICacheableRouteRegistrar cacheAbleRouteRegistrar) { - this._settingService = settingService; + _settingService = settingService; + _cacheableRouteRegistrar = cacheAbleRouteRegistrar; + this.Logger = NullLogger.Instance; } public ILogger Logger { get; set; } - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + //public IList GetWidgetZones() => new List { "home_page_top" }; + + //public void GetDisplayWidgetRoute(string widgetZone, object model, int storeId, out string actionName, out string controllerName, out RouteValueDictionary routeValues) + //{ + // actionName = "MyDemoWidget"; + // controllerName = "DevTools"; + + // routeValues = new RouteValueDictionary + // { + // { "Namespaces", "SmartStore.DevTools.Controllers" }, + // { "area", "SmartStore.DevTools" } + // }; + //} + + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "DevTools"; @@ -31,8 +53,12 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa public override void Install() { + // Example for how to add a route to the output cache + //_cacheableRouteRegistrar.RegisterCacheableRoute("SmartStore.DevTools/DevTools/PublicInfo"); + _settingService.SaveSetting(new ProfilerSettings()); base.Install(); + Logger.Info(string.Format("Plugin installed: SystemName: {0}, Version: {1}, Description: '{2}'", PluginDescriptor.SystemName, PluginDescriptor.Version, PluginDescriptor.FriendlyName)); } @@ -41,6 +67,9 @@ public override void Install() ///
public override void Uninstall() { + // Example for how to remove a route from the output cache + //_cacheableRouteRegistrar.RemoveCacheableRoute("SmartStore.DevTools/DevTools/PublicInfo"); + _settingService.DeleteSetting(); base.Uninstall(); } @@ -72,5 +101,5 @@ internal static bool HasPendingMigrations() return result; } - } + } } diff --git a/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs b/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs new file mode 100644 index 0000000000..9c5bcda2a3 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Examples/OutputCacheInvalidationObserver.cs @@ -0,0 +1,89 @@ +using SmartStore.Core; +using SmartStore.Core.Caching; +using SmartStore.Core.Infrastructure; +using SmartStore.Services; +using SmartStore.Services.Catalog; +using SmartStore.Services.Topics; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.DevTools.Examples +{ + public static class OutputCacheInvalidationObserver + { + public static void Execute() + { + // Register a handler which returns unique string tags for your custom entity. + // This is required if you ever call IDisplayControl.Announce(entity) for your entity, + // otherwise the system is not able to generate tags. + // Tags can be obtained via IDisplayControl.GetCacheControlTagsFor(entity). + // Invalidation by tags is done via IOutputCacheProvider.InvalidateByTag(string[] tags). + // InvalidateByTag() will remove any page from the output cache storage in which your custom entity is somehow displayed. + DisplayControl.RegisterHandlerFor(typeof(MyRecord), (x, c) => new[] { "mr" + x.Id }); + + // Register an output cache observe handler + var observer = EngineContext.Current.Resolve(); + observer.ObserveEntity(Observe); + } + + private static void Observe(ObserveEntityContext ctx) + { + // If your plugin renders dynamic content to the frontend which won't change often once it is configured, you should add a corresponding route + // to the ouput cache to add the rendered output of the plugin to the cache. For more information on this see the Install/Uninstall method of this plugin + + // In the example below we assume the plugin has own entities which depend on system entities like Product, Category or Topic. + // In this case you must invalidate the cache items for the system entities as soon as the corresponding plugin entity is changed by the shop admin + // in order to remove the cached output of the affected pages in the frontend and thus be rebuilt + + var myRecord = ctx?.Entity as MyRecord; + + if (myRecord != null) + { + IEnumerable tags = null; + + var outputCacheProvider = ctx.OutputCacheProvider; + + // We assume the domain record from the plugin stores information about the type of the entity in EntityName and the corresponding Id in EntityId + var entityname = myRecord.EntityName; + + // Collect the tags for the entities which must be invalidated due to changes of the plugin domain record + switch (entityname.ToLower()) + { + case "product": + var product = ctx.ServiceContainer.Resolve().GetProductById(myRecord.EntityId); + if (product != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(product); + break; + case "category": + var category = ctx.ServiceContainer.Resolve().GetCategoryById(myRecord.EntityId); + if (category != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(category); + break; + case "topic": + var topic = ctx.ServiceContainer.Resolve().GetTopicById(myRecord.EntityId); + if (topic != null) tags = ctx.DisplayControl.GetCacheControlTagsFor(topic); + break; + } + + // Invalidate cache items by the collected tags + if (tags != null && tags.Any()) + { + outputCacheProvider.InvalidateByTag(tags.ToArray()); + ctx.Handled = true; + } + } + } + + internal class MyRecord : BaseEntity + { + public int EntityId + { + get { return 0; } + } + + public string EntityName + { + get { return String.Empty; } + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs index 546df864a1..c71e82d5fe 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/MachineNameFilter.cs @@ -17,9 +17,9 @@ public MachineNameFilter( Lazy widgetProvider, ProfilerSettings profilerSettings) { - this._services = services; - this._widgetProvider = widgetProvider; - this._profilerSettings = profilerSettings; + _services = services; + _widgetProvider = widgetProvider; + _profilerSettings = profilerSettings; } public void OnResultExecuting(ResultExecutingContext filterContext) diff --git a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs index 03f37f1ece..fb4307fc1a 100644 --- a/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs +++ b/src/Plugins/SmartStore.DevTools/Filters/Samples/SampleProductDetailActionFilter.cs @@ -56,7 +56,7 @@ public void OnActionExecuted(ActionExecutedContext filterContext) CssClass = "action-dev x-ajax-cart-link", IconCssClass = "icm icm-code", //Href = _urlHelper.Action("MyOwnAction", "MyPlugin", new { id = model.Id }) - Href = "http://www.smartstore.com" + Href = "https://www.smartstore.com" }; ; } } diff --git a/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml b/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml index 82fb7ec059..67cf6df4a5 100644 --- a/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.DevTools/Localization/resources.de-de.xml @@ -1,4 +1,8 @@  + + Dev-Tools Demo Widget + + Mini-Profiler für Frontend aktivieren diff --git a/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml b/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml index b292c239a0..0ed80f9443 100644 --- a/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.DevTools/Localization/resources.en-us.xml @@ -1,4 +1,8 @@  + + Dev-Tools Demo Widget + + Enable Mini-Profiler in public store diff --git a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj index 443061f98f..e08ca063bb 100644 --- a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj +++ b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj @@ -84,11 +84,9 @@ ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - True ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll - True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -151,6 +149,7 @@ + diff --git a/src/Plugins/SmartStore.DevTools/Starter.cs b/src/Plugins/SmartStore.DevTools/Starter.cs index 6b1daa4e0b..83f335c991 100644 --- a/src/Plugins/SmartStore.DevTools/Starter.cs +++ b/src/Plugins/SmartStore.DevTools/Starter.cs @@ -1,15 +1,14 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using Microsoft.Web.Infrastructure.DynamicModuleHelper; +using Microsoft.Web.Infrastructure.DynamicModuleHelper; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.Web.Framework; using StackExchange.Profiling; using StackExchange.Profiling.Storage; +using System; +using System.Collections.Generic; namespace SmartStore.DevTools -{ +{ public class ProfilerPreApplicationStart : IPreApplicationStart { public void Start() @@ -28,8 +27,11 @@ public void Execute() //StackExchange.Profiling.MiniProfiler.Settings.Storage = new NullProfilerStorage(); StackExchange.Profiling.EntityFramework6.MiniProfilerEF6.Initialize(); + + // output cache invidation example + //OutputCacheInvalidationObserver.Execute(); } - + public int Order { get { return int.MinValue; } diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index 155d8240c8..a399d1f92b 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,8 +2,8 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll ResourceRootKey: Plugins.SmartStore.DiscountRules diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/BillingCountry.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/BillingCountry.cshtml index 1fef163c8a..93f895d970 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/BillingCountry.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/BillingCountry.cshtml @@ -5,7 +5,7 @@ } - + @@ -14,13 +14,13 @@ data-discount-id='@Model.DiscountId' data-requirement-id='@Model.RequirementId' data-action-url='@Url.Action("BillingCountry")' - data-fail-msg='@T("Admin.Promotions.Discounts.Requirements.FailedToSave").Text.EncodeJsString()' - data-success-msg='@T("Admin.Promotions.Discounts.Requirements.Saved").Text.EncodeJsString()'> + data-fail-msg=@T("Admin.Promotions.Discounts.Requirements.FailedToSave").JsText + data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
@Html.DropDownListFor(model => model.CountryId, Model.AvailableCountries, new { data_routeparam = "countryId" }) -
diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/CustomerRole.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/CustomerRole.cshtml index 1374894a2d..7353d01ea2 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/CustomerRole.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/CustomerRole.cshtml @@ -5,7 +5,7 @@ @using SmartStore.Web.Framework;
@Html.SmartLabelFor(model => model.CountryId)
- + @@ -14,13 +14,13 @@ data-discount-id='@Model.DiscountId' data-requirement-id='@Model.RequirementId' data-action-url='@Url.Action("CustomerRole")' - data-fail-msg='@T("Admin.Promotions.Discounts.Requirements.FailedToSave").Text.EncodeJsString()' - data-success-msg='@T("Admin.Promotions.Discounts.Requirements.Saved").Text.EncodeJsString()'> + data-fail-msg=@T("Admin.Promotions.Discounts.Requirements.FailedToSave").JsText + data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
@Html.DropDownListFor(model => model.CustomerRoleId, Model.AvailableCustomerRoles, new { data_routeparam = "customerRoleId" }) -
diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml index b40c8f2531..2bd80940da 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HadSpentAmount.cshtml @@ -27,9 +27,9 @@ data-discount-id='@Model.DiscountId' data-requirement-id='@Model.RequirementId' data-action-url='@Url.Action("HadSpentAmount")' - data-fail-msg='@T("Admin.Promotions.Discounts.Requirements.FailedToSave").Text.EncodeJsString()' - data-success-msg='@T("Admin.Promotions.Discounts.Requirements.Saved").Text.EncodeJsString()'> - + data-fail-msg=@T("Admin.Promotions.Discounts.Requirements.FailedToSave").JsText + data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText> + @@ -39,7 +39,7 @@ @Html.ValidationMessageFor(model => model.SpentAmount) - + @@ -48,14 +48,14 @@ @Html.ValidationMessageFor(model => model.LimitToCurrentBasketSubTotal) - + diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasAllProducts.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasAllProducts.cshtml index d1d8d4fb1d..20cc92462e 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasAllProducts.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasAllProducts.cshtml @@ -4,7 +4,7 @@ Layout = ""; }
@Html.SmartLabelFor(model => model.CustomerRoleId)
@Html.SmartLabelFor(model => model.SpentAmount)
@Html.SmartLabelFor(model => model.LimitToCurrentBasketSubTotal)
  -
- + @@ -17,7 +17,14 @@ data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
- @Html.TextBoxFor(model => model.Products, new { data_routeparam = "productIds" }) + @Html.TextBoxFor(model => model.Products, new { data_routeparam = "productIds", @readonly = "readonly" }) + + + + + + +
@(Html.SmartStore().EntityPicker() .For(x => x.Products) @@ -27,12 +34,22 @@
-
-
@Html.SmartLabelFor(model => model.Products)
\ No newline at end of file + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasOneProduct.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasOneProduct.cshtml index 0a7c599eea..f1582e8eb9 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasOneProduct.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/HasOneProduct.cshtml @@ -4,7 +4,7 @@ Layout = ""; } - + @@ -17,7 +17,12 @@ data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
- @Html.TextBoxFor(model => model.Products, new { data_routeparam = "productIds" }) + @Html.TextBoxFor(model => model.Products, new { data_routeparam = "productIds", @readonly = "readonly" }) + + + + +
@(Html.SmartStore().EntityPicker() .For(x => x.Products) @@ -27,12 +32,22 @@
-
-
@Html.SmartLabelFor(model => model.Products)
\ No newline at end of file + + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/ShippingCountry.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/ShippingCountry.cshtml index c28de4d55b..e9685aa7e9 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/ShippingCountry.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/ShippingCountry.cshtml @@ -5,7 +5,7 @@ @using SmartStore.Web.Framework; - + @@ -14,13 +14,13 @@ data-discount-id='@Model.DiscountId' data-requirement-id='@Model.RequirementId' data-action-url='@Url.Action("ShippingCountry")' - data-fail-msg='@T("Admin.Promotions.Discounts.Requirements.FailedToSave").Text.EncodeJsString()' - data-success-msg='@T("Admin.Promotions.Discounts.Requirements.Saved").Text.EncodeJsString()'> + data-fail-msg=@T("Admin.Promotions.Discounts.Requirements.FailedToSave").JsText + data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
@Html.DropDownListFor(model => model.CountryId, Model.AvailableCountries, new { data_routeparam = "countryId" }) -
diff --git a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/Store.cshtml b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/Store.cshtml index fc599ba2ca..3c91814931 100644 --- a/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/Store.cshtml +++ b/src/Plugins/SmartStore.DiscountRules/Views/DiscountRules/Store.cshtml @@ -5,7 +5,7 @@ }
@Html.SmartLabelFor(model => model.CountryId)
- + @@ -14,13 +14,13 @@ data-discount-id='@Model.DiscountId' data-requirement-id='@Model.RequirementId' data-action-url='@Url.Action("Store")' - data-fail-msg='@T("Admin.Promotions.Discounts.Requirements.FailedToSave").Text.EncodeJsString()' - data-success-msg='@T("Admin.Promotions.Discounts.Requirements.Saved").Text.EncodeJsString()'> + data-fail-msg=@T("Admin.Promotions.Discounts.Requirements.FailedToSave").JsText + data-success-msg=@T("Admin.Promotions.Discounts.Requirements.Saved").JsText>
@Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, new { data_routeparam = "storeId" }) -
diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index 53b0701b3e..96ed94260a 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -13,7 +13,7 @@ namespace SmartStore.FacebookAuth.Controllers { - //[UnitOfWork] + //[UnitOfWork] public class ExternalAuthFacebookController : PluginControllerBase { private readonly IOAuthProviderFacebookAuthorizer _oAuthProviderFacebookAuthorizer; @@ -35,7 +35,7 @@ public ExternalAuthFacebookController( private bool HasPermission(bool notify = true) { - bool hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods); + var hasPermission = _services.Permissions.Authorize(StandardPermissionProvider.ManageExternalAuthenticationMethods); if (notify && !hasPermission) NotifyError(_services.Localization.GetResource("Admin.AccessDenied.Description")); @@ -51,7 +51,11 @@ public ActionResult Configure(FacebookExternalAuthSettings settings) var model = new ConfigurationModel(); MiniMapper.Map(settings, model); - return View(model); + + var host = _services.StoreContext.CurrentStore.GetHost(true); + model.RedirectUrl = $"{host}Plugins/SmartStore.FacebookAuth/logincallback/"; + + return View(model); } [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] @@ -64,9 +68,12 @@ public ActionResult Configure(FacebookExternalAuthSettings settings, Configurati return Configure(settings); MiniMapper.Map(model, settings); + settings.ClientKeyIdentifier = model.ClientKeyIdentifier.TrimSafe(); + settings.ClientSecret = model.ClientSecret.TrimSafe(); + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); - return Configure(settings); + return RedirectToConfiguration(FacebookExternalAuthMethod.SystemName, true); } [ChildActionOnly] @@ -93,7 +100,7 @@ public ActionResult LoginCallback(string returnUrl) [NonAction] private ActionResult LoginInternal(string returnUrl, bool verifyResponse) { - var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(Provider.SystemName, _services.StoreContext.CurrentStore.Id); + var processor = _openAuthenticationService.LoadExternalAuthenticationMethodBySystemName(FacebookExternalAuthMethod.SystemName, _services.StoreContext.CurrentStore.Id); if (processor == null || !processor.IsMethodActive(_externalAuthenticationSettings)) { throw new SmartException("Facebook module cannot be loaded"); @@ -108,8 +115,9 @@ private ActionResult LoginInternal(string returnUrl, bool verifyResponse) case OpenAuthenticationStatus.Error: { if (!result.Success) - foreach (var error in result.Errors) - NotifyError(error); + { + result.Errors.Each(x => NotifyError(x)); + } return new RedirectResult(Url.LogOn(returnUrl)); } diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs index df2287b070..e11239ef4f 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookOAuth2Client.cs @@ -123,8 +123,7 @@ public override AuthenticationResult VerifyAuthentication(HttpContextBase contex // add the access token to the user data dictionary just in case page developers want to use it userData["accesstoken"] = accessToken; - return new AuthenticationResult( - isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData); + return new AuthenticationResult(isSuccessful: true, provider: ProviderName, providerUserId: id, userName: name, extraData: userData); } protected override Uri GetServiceLoginUrl(Uri returnUrl) @@ -182,21 +181,24 @@ protected override string QueryAccessToken(Uri returnUrl, string authorizationCo var webRequest = (HttpWebRequest)WebRequest.Create(uri); string accessToken = null; - HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse(); - // handle response from FB - // this will not be a url with params like the first request to get the 'code' - Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet); - - using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding)) + using (var response = (HttpWebResponse)webRequest.GetResponse()) { - var serializer = new JavaScriptSerializer(); - var jsonObject = serializer.DeserializeObject(sr.ReadToEnd()); - var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject)); + // handle response from FB + // this will not be a url with params like the first request to get the 'code' + Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet); + + using (var sr = new StreamReader(response.GetResponseStream(), rEncoding)) + { + var serializer = new JavaScriptSerializer(); + var jsonObject = serializer.DeserializeObject(sr.ReadToEnd()); + var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject)); - Dictionary desirializedJsonObject = JsonConvert.DeserializeObject>(jConvert.ToString()); - accessToken = desirializedJsonObject["access_token"].ToString(); + Dictionary desirializedJsonObject = JsonConvert.DeserializeObject>(jConvert.ToString()); + accessToken = desirializedJsonObject["access_token"].ToString(); + } } + return accessToken; } diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs index fe65c7bf72..6f987af313 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs @@ -11,6 +11,7 @@ using DotNetOpenAuth.AspNet; using Newtonsoft.Json.Linq; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Authentication.External; @@ -38,12 +39,16 @@ public FacebookProviderAuthorizer(IExternalAuthorizer authorizer, HttpContextBase httpContext, ICommonServices services) { - this._authorizer = authorizer; - this._openAuthenticationService = openAuthenticationService; - this._externalAuthenticationSettings = externalAuthenticationSettings; - this._httpContext = httpContext; - this._services = services; - } + _authorizer = authorizer; + _openAuthenticationService = openAuthenticationService; + _externalAuthenticationSettings = externalAuthenticationSettings; + _httpContext = httpContext; + _services = services; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } #endregion @@ -66,9 +71,34 @@ private FacebookOAuth2Client FacebookApplication private AuthorizeState VerifyAuthentication(string returnUrl) { - var authResult = this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri()); + string error = null; + AuthenticationResult authResult = null; + + try + { + authResult = this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri()); + } + catch (WebException wexc) + { + using (var response = wexc.Response as HttpWebResponse) + { + error = response.StatusDescription; + + var enc = Encoding.GetEncoding(response.CharacterSet); + using (var reader = new StreamReader(response.GetResponseStream(), enc)) + { + var rawResponse = reader.ReadToEnd(); + Logger.Log(LogLevel.Error, new Exception(rawResponse), response.StatusDescription, null); + } + } + } + catch (Exception exception) + { + error = exception.ToString(); + Logger.Log(LogLevel.Error, exception, null, null); + } - if (authResult.IsSuccessful) + if (authResult != null && authResult.IsSuccessful) { if (!authResult.ExtraData.ContainsKey("id")) throw new Exception("Authentication result does not contain id data"); @@ -76,7 +106,7 @@ private AuthorizeState VerifyAuthentication(string returnUrl) if (!authResult.ExtraData.ContainsKey("accesstoken")) throw new Exception("Authentication result does not contain accesstoken data"); - var parameters = new OAuthAuthenticationParameters(Provider.SystemName) + var parameters = new OAuthAuthenticationParameters(FacebookExternalAuthMethod.SystemName) { ExternalIdentifier = authResult.ProviderUserId, OAuthToken = authResult.ExtraData["accesstoken"], @@ -91,11 +121,17 @@ private AuthorizeState VerifyAuthentication(string returnUrl) return new AuthorizeState(returnUrl, result); } - var state = new AuthorizeState(returnUrl, OpenAuthenticationStatus.Error); + if (error.IsEmpty() && authResult != null && authResult.Error != null) + { + error = authResult.Error.Message; + } + if (error.IsEmpty()) + { + error = _services.Localization.GetResource("Admin.Common.UnknownError"); + } - state.AddError(authResult.Error != null - ? authResult.Error.Message - : _services.Localization.GetResource("Admin.Common.UnknownError")); + var state = new AuthorizeState(returnUrl, OpenAuthenticationStatus.Error); + state.AddError(error); return state; } diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs b/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs deleted file mode 100644 index 61ed3cde9c..0000000000 --- a/src/Plugins/SmartStore.FacebookAuth/Core/Provider.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace SmartStore.FacebookAuth.Core -{ - public static class Provider - { - public static string SystemName - { - get - { - return "SmartStore.FacebookAuth"; - } - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index 200d651415..514333f075 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll ResourceRootKey: Plugins.ExternalAuth.Facebook \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs index d3a0ea7b26..86ed1ab677 100644 --- a/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs +++ b/src/Plugins/SmartStore.FacebookAuth/FacebookExternalAuthMethod.cs @@ -6,41 +6,33 @@ namespace SmartStore.FacebookAuth { - /// - /// Facebook externalAuth processor - /// - public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable + /// + /// Facebook externalAuth processor + /// + public class FacebookExternalAuthMethod : BasePlugin, IExternalAuthenticationMethod, IConfigurable { - #region Fields - private readonly FacebookExternalAuthSettings _facebookExternalAuthSettings; private readonly ILocalizationService _localizationService; - #endregion - - #region Ctor - public FacebookExternalAuthMethod(FacebookExternalAuthSettings facebookExternalAuthSettings, ILocalizationService localizationService) { - this._facebookExternalAuthSettings = facebookExternalAuthSettings; + _facebookExternalAuthSettings = facebookExternalAuthSettings; _localizationService = localizationService; } - #endregion + public static string SystemName => "SmartStore.FacebookAuth"; - #region Methods - - /// - /// Gets a route for provider configuration - /// - /// Action name - /// Controller name - /// Route values - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + /// + /// Gets a route for provider configuration + /// + /// Action name + /// Controller name + /// Route values + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = SystemName }); } /// @@ -53,7 +45,7 @@ public void GetPublicInfoRoute(out string actionName, out string controllerName, { actionName = "PublicInfo"; controllerName = "ExternalAuthFacebook"; - routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = Provider.SystemName }); + routeValues = new RouteValueDictionary(new { Namespaces = "SmartStore.FacebookAuth.Controllers", area = SystemName }); } /// @@ -61,21 +53,16 @@ public void GetPublicInfoRoute(out string actionName, out string controllerName, /// public override void Install() { - //locales - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); + _localizationService.ImportPluginResourcesFromXml(PluginDescriptor); base.Install(); } public override void Uninstall() { - //locales - _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); + _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); base.Uninstall(); - } - - #endregion - + } } } diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml index 34d1ab5e11..6839a4255c 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.de-de.xml @@ -1,28 +1,35 @@  - - Facebook - + + Facebook + - -
  • Die Facebook Zugangsdaten (s.u.) erhalten Sie, indem Sie bei Facebook unter Entwickler eine Anwendung mit der Option Facebook-Anmeldung erstellen.
  • -
  • Aktivieren Sie in den Kundeneinstellungen die automatische Registrierung, falls extern autorisierte Besucher automatisch registriert werden sollen.
  • -]]> + So richten Sie die Facebook Authentifizierungen ein:

      +
    • Erstellen Sie bei Facebook eine neue App vom Typ Facebook Login. Deren Zugangsdaten tragen Sie bitte unten in das Formular ein.
    • +
    • Unter Facebook Login > Einstellungen > Gültige OAuth Redirect URIs wird die unten aufgeführte URL eingetragen.
    • +
    • Aktivieren Sie in den Kundeneinstellungen des Shops die automatische Registrierung, falls extern autorisierte Besucher automatisch registriert werden sollen.
    • +
    ]]>
    - Mit Facebook anmelden - - - Benutzer-Schlüssel-Identifikator (App ID) - - - Geben Sie hier Ihren Benutzer-Schlüssel-Identifikator (App ID) an. - - - Benutzer-Sicherheitsschlüssel (App Secret) - - - Geben Sie hier Ihren Benutzer-Sicherheitsschlüssel (App Secret) an. - + Mit Facebook anmelden + + + Benutzer-Schlüssel-Identifikator (App ID) + + + Geben Sie hier Ihren Benutzer-Schlüssel-Identifikator (App ID) an. + + + Benutzer-Sicherheitsschlüssel (App Secret) + + + Geben Sie hier Ihren Benutzer-Sicherheitsschlüssel (App Secret) an. + + + Weiterleitungs URL (OAuth Redirect URI) + + + Die bei Facebook einzutragende Weiterleitungs URL (OAuth Redirect URI). +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml index e1594d213b..fdf0710bfa 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.FacebookAuth/Localization/resources.en-us.xml @@ -1,28 +1,35 @@  - - Facebook - + + Facebook + - -
  • Create an application with the facebook login option in the developer area at facebook to get the facebook access data (see below).
  • -
  • Activate auto registration in the customer settings if you want externally authorized visitors to be registered automatically.
  • -]]> + How to set up Facebook authentication:

      +
    • Create a new app on Facebook of type Facebook Login. Please enter their access data in the form below.
    • +
    • Enter the URL listed below under Facebook Login > Settings > Valid OAuth Redirect URIs.
    • +
    • Activate automatic registration in the shop's customer settings if you want to register externally authorized visitors automatically.
    • +
    ]]>
    - - Sign in with Facebook - - - Client key identifier (App ID) - - - Enter your client key identifier (App ID) here. - - - Client secret (App Secret) - - - Enter your client secret (App Secret) here. - + + Sign in with Facebook + + + Client key identifier (App ID) + + + Enter your client key identifier (App ID) here. + + + Client secret (App Secret) + + + Enter your client secret (App Secret) here. + + + OAuth Redirect URI + + + The OAuth Redirect URI to be entered on Facebook. +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs index 928feab5b2..dc0a098627 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Models/ConfigurationModel.cs @@ -10,5 +10,8 @@ public class ConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.ClientSecret")] public string ClientSecret { get; set; } - } + + [SmartResourceDisplayName("Plugins.ExternalAuth.Facebook.RedirectUri")] + public string RedirectUrl { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs index 90465eb41a..10f1f4c1ac 100644 --- a/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs +++ b/src/Plugins/SmartStore.FacebookAuth/RouteProvider.cs @@ -1,11 +1,10 @@ using System.Web.Mvc; using System.Web.Routing; -using SmartStore.FacebookAuth.Core; using SmartStore.Web.Framework.Routing; namespace SmartStore.FacebookAuth { - public partial class RouteProvider : IRouteProvider + public partial class RouteProvider : IRouteProvider { public void RegisterRoutes(RouteCollection routes) { @@ -14,7 +13,7 @@ public void RegisterRoutes(RouteCollection routes) new { controller = "ExternalAuthFacebook" }, new[] { "SmartStore.FacebookAuth.Controllers" } ) - .DataTokens["area"] = Provider.SystemName; + .DataTokens["area"] = FacebookExternalAuthMethod.SystemName; } public int Priority { diff --git a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj index 826a86e041..41e2ae7d49 100644 --- a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj +++ b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj @@ -45,6 +45,7 @@ + true @@ -183,7 +184,6 @@ - diff --git a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml index f0323d436a..1658e3b419 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml +++ b/src/Plugins/SmartStore.FacebookAuth/Views/ExternalAuthFacebook/Configure.cshtml @@ -40,5 +40,13 @@ @Html.ValidationMessageFor(model => model.ClientSecret)
    + + + +
    @Html.SmartLabelFor(model => model.StoreId)
    + @Html.SmartLabelFor(model => model.RedirectUrl) + + @Html.TextBoxFor(model => model.RedirectUrl, new { @readonly = "readonly", @class = "form-control-plaintext" }) +
    } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png b/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png index 5a8e11a77b..7e3e7da076 100644 Binary files a/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png and b/src/Plugins/SmartStore.GoogleAnalytics/Content/icon.png differ diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs index a16493baf0..bd0551c79b 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs @@ -3,73 +3,78 @@ using System.Linq; using System.Text; using System.Web.Mvc; +using SmartStore.ComponentModel; using SmartStore.Core; -using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.GoogleAnalytics.Models; using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; -using SmartStore.Core.Logging; using SmartStore.Services.Orders; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; -using SmartStore.ComponentModel; namespace SmartStore.GoogleAnalytics.Controllers { - public class WidgetsGoogleAnalyticsController : SmartController + public class WidgetsGoogleAnalyticsController : SmartController { private readonly IWorkContext _workContext; private readonly IStoreContext _storeContext; - private readonly IStoreService _storeService; private readonly ISettingService _settingService; private readonly IOrderService _orderService; private readonly ICategoryService _categoryService; - public WidgetsGoogleAnalyticsController(IWorkContext workContext, - IStoreContext storeContext, IStoreService storeService, - ISettingService settingService, IOrderService orderService, + public WidgetsGoogleAnalyticsController( + IWorkContext workContext, + IStoreContext storeContext, + ISettingService settingService, + IOrderService orderService, ICategoryService categoryService) { - this._workContext = workContext; - this._storeContext = storeContext; - this._storeService = storeService; - this._settingService = settingService; - this._orderService = orderService; - this._categoryService = categoryService; - } - - [AdminAuthorize] - [LoadSetting, ChildActionOnly] + _workContext = workContext; + _storeContext = storeContext; + _settingService = settingService; + _orderService = orderService; + _categoryService = categoryService; + } + + [AdminAuthorize, ChildActionOnly, LoadSetting] public ActionResult Configure(GoogleAnalyticsSettings settings) { var model = new ConfigurationModel(); MiniMapper.Map(settings, model); model.ZoneId = settings.WidgetZone; - model.AvailableZones.Add(new SelectListItem() { Text = " HTML tag", Value = "head_html_tag"}); - model.AvailableZones.Add(new SelectListItem() { Text = "Before end HTML tag", Value = "body_end_html_tag_before" }); + model.AvailableZones.Add(new SelectListItem { Text = " HTML tag", Value = "head_html_tag"}); + model.AvailableZones.Add(new SelectListItem { Text = "Before end HTML tag", Value = "body_end_html_tag_before" }); return View(model); } - [HttpPost] - [AdminAuthorize] - [SaveSetting, ChildActionOnly] - [ValidateInput(false)] - public ActionResult Configure(GoogleAnalyticsSettings settings, ConfigurationModel model) + [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + public ActionResult Configure(ConfigurationModel model, FormCollection form) { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + ModelState.Clear(); MiniMapper.Map(model, settings); settings.WidgetZone = model.ZoneId; - _settingService.SaveSetting(settings, x => x.WidgetZone, 0, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } + + using (Services.Settings.BeginScope()) + { + _settingService.SaveSetting(settings, x => x.WidgetZone, 0, false); + } - return Configure(settings); + return RedirectToConfiguration("SmartStore.GoogleAnalytics"); } [ChildActionOnly] @@ -79,9 +84,9 @@ public ActionResult PublicInfo(string widgetZone) var routeData = ((System.Web.UI.Page)this.HttpContext.CurrentHandler).RouteData; try - { - //Special case, if we are in last step of checkout, we can use order total for conversion value - if (routeData.Values["controller"].ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && + { + // Special case, if we are in last step of checkout, we can use order total for conversion value + if (routeData.Values["controller"].ToString().Equals("checkout", StringComparison.InvariantCultureIgnoreCase) && routeData.Values["action"].ToString().Equals("completed", StringComparison.InvariantCultureIgnoreCase)) { var lastOrder = GetLastOrder(); @@ -105,67 +110,104 @@ private Order GetLastOrder() null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); return order; } - - private string GetTrackingScript() + + private string GetOptOutCookieScript() + { + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var script = @" + var gaProperty = '{GOOGLEID}'; + var disableStr = 'ga-disable-' + gaProperty; + if (document.cookie.indexOf(disableStr + '=true') > -1) { + window[disableStr] = true; + } + function gaOptout() { + document.cookie = disableStr + '=true; expires=Thu, 31 Dec 2099 23:59:59 UTC; path=/'; + window[disableStr] = true; + alert('{NOTIFICATION}'); + } + "; + + script = script + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{NOTIFICATION}", T("Plugins.Widgets.GoogleAnalytics.OptOutNotification").JsText.ToHtmlString()); + + return script; + } + + private string GetTrackingScript() { - var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); - string analyticsTrackingScript = ""; - analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; - analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", ""); - return analyticsTrackingScript; + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var script = ""; + script = settings.TrackingScript + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{ECOMMERCE}", ""); + script = script.Replace("{OPTOUTCOOKIE}", GetOptOutCookieScript()); + + return script; } private string GetEcommerceScript(Order order) { - var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); + var settings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); var usCulture = new CultureInfo("en-US"); - string analyticsTrackingScript = ""; - analyticsTrackingScript = googleAnalyticsSettings.TrackingScript + "\n"; - analyticsTrackingScript = analyticsTrackingScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); + var script = ""; + var ecScript = ""; + + script = settings.TrackingScript + "\n"; + script = script.Replace("{GOOGLEID}", settings.GoogleId); + script = script.Replace("{OPTOUTCOOKIE}", GetOptOutCookieScript()); - string analyticsEcommerceScript = ""; - if (order != null) + if (order != null) { - analyticsEcommerceScript = googleAnalyticsSettings.EcommerceScript + "\n"; - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{GOOGLEID}", googleAnalyticsSettings.GoogleId); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{ORDERID}", order.GetOrderNumber()); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SITE}", _storeContext.CurrentStore.Url.Replace("http://", "").Replace("/", "")); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{SHIP}", order.OrderShippingInclTax.ToString("0.00", usCulture)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CURRENCY}", order.CustomerCurrencyCode); + var site = _storeContext.CurrentStore.Url + .EmptyNull() + .Replace("http://", "") + .Replace("https://", "") + .Replace("/", ""); + + ecScript = settings.EcommerceScript + "\n"; + ecScript = ecScript.Replace("{GOOGLEID}", settings.GoogleId); + ecScript = ecScript.Replace("{ORDERID}", order.GetOrderNumber()); + ecScript = ecScript.Replace("{SITE}", FixIllegalJavaScriptChars(site)); + ecScript = ecScript.Replace("{TOTAL}", order.OrderTotal.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{TAX}", order.OrderTax.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{SHIP}", order.OrderShippingInclTax.ToString("0.00", usCulture)); + ecScript = ecScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); + ecScript = ecScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null + ? "" + : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); + ecScript = ecScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null + ? "" + : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); + ecScript = ecScript.Replace("{CURRENCY}", order.CustomerCurrencyCode); var sb = new StringBuilder(); foreach (var item in order.OrderItems) { - string analyticsEcommerceDetailScript = googleAnalyticsSettings.EcommerceDetailScript; - //get category - string categ = ""; + var ecDetailScript = settings.EcommerceDetailScript; var defaultProductCategory = _categoryService.GetProductCategoriesByProductId(item.ProductId).FirstOrDefault(); - if (defaultProductCategory != null) - categ = defaultProductCategory.Category.Name; - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{ORDERID}", order.GetOrderNumber()); - //The SKU code is a required parameter for every item that is added to the transaction - item.Product.MergeWithCombination(item.AttributesXml); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.Sku)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(categ)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{UNITPRICE}", item.UnitPriceInclTax.ToString("0.00", usCulture)); - analyticsEcommerceDetailScript = analyticsEcommerceDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); - sb.AppendLine(analyticsEcommerceDetailScript); - } + var categoryName = defaultProductCategory != null + ? defaultProductCategory.Category.Name + : ""; + + // The SKU code is a required parameter for every item that is added to the transaction. + item.Product.MergeWithCombination(item.AttributesXml); - analyticsEcommerceScript = analyticsEcommerceScript.Replace("{DETAILS}", sb.ToString()); + ecDetailScript = ecDetailScript.Replace("{ORDERID}", order.GetOrderNumber()); + ecDetailScript = ecDetailScript.Replace("{PRODUCTSKU}", FixIllegalJavaScriptChars(item.Product.Sku)); + ecDetailScript = ecDetailScript.Replace("{PRODUCTNAME}", FixIllegalJavaScriptChars(item.Product.Name)); + ecDetailScript = ecDetailScript.Replace("{CATEGORYNAME}", FixIllegalJavaScriptChars(categoryName)); + ecDetailScript = ecDetailScript.Replace("{UNITPRICE}", item.UnitPriceInclTax.ToString("0.00", usCulture)); + ecDetailScript = ecDetailScript.Replace("{QUANTITY}", item.Quantity.ToString()); - analyticsTrackingScript = analyticsTrackingScript.Replace("{ECOMMERCE}", analyticsEcommerceScript); + sb.AppendLine(ecDetailScript); + } + ecScript = ecScript.Replace("{DETAILS}", sb.ToString()); + script = script.Replace("{ECOMMERCE}", ecScript); } - return analyticsTrackingScript; + return script; } private string FixIllegalJavaScriptChars(string text) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index 8e4c058c46..c12d33b49a 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll ResourceRootKey: Plugins.Widgets.GoogleAnalytics diff --git a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs index 2363cb6add..87f8f2f2b4 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs @@ -7,139 +7,139 @@ namespace SmartStore.GoogleAnalytics { - /// - /// Google Analytics Plugin - /// + /// + /// Google Analytics Plugin + /// public class GoogleAnalyticPlugin : BasePlugin, IWidget, IConfigurable - { - private readonly ISettingService _settingService; - private readonly GoogleAnalyticsSettings _googleAnalyticsSettings; - private readonly ILocalizationService _localizationService; - - public GoogleAnalyticPlugin(ISettingService settingService, - GoogleAnalyticsSettings googleAnalyticsSettings, - ILocalizationService localizationService) - { - this._settingService = settingService; - this._googleAnalyticsSettings = googleAnalyticsSettings; - _localizationService = localizationService; - } - - /// - /// Gets widget zones where this widget should be rendered - /// - /// Widget zones - public IList GetWidgetZones() - { - var zones = new List() { "head_html_tag" }; - if(!string.IsNullOrWhiteSpace(_googleAnalyticsSettings.WidgetZone)) - { - zones = new List() { - _googleAnalyticsSettings.WidgetZone - }; - } - - return zones; - } - - /// - /// Gets a route for provider configuration - /// - /// Action name - /// Controller name - /// Route values - public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) - { - actionName = "Configure"; - controllerName = "WidgetsGoogleAnalytics"; + { + #region Scripts + + private const string TRACKING_SCRIPT = @" +"; + + private const string ECOMMERCE_SCRIPT = @"ga('require', 'ecommerce'); +ga('ecommerce:addTransaction', { + 'id': '{ORDERID}', + 'affiliation': '{SITE}', + 'revenue': '{TOTAL}', + 'shipping': '{SHIP}', + 'tax': '{TAX}', + 'currency': '{CURRENCY}' +}); + +{DETAILS} + +ga('ecommerce:send');"; + + private const string ECOMMERCE_DETAIL_SCRIPT = @"ga('ecommerce:addItem', { + 'id': '{ORDERID}', + 'name': '{PRODUCTNAME}', + 'sku': '{PRODUCTSKU}', + 'category': '{CATEGORYNAME}', + 'price': '{UNITPRICE}', + 'quantity': '{QUANTITY}' +});"; + + #endregion + + private readonly ISettingService _settingService; + private readonly GoogleAnalyticsSettings _googleAnalyticsSettings; + private readonly ILocalizationService _localizationService; + + public GoogleAnalyticPlugin(ISettingService settingService, + GoogleAnalyticsSettings googleAnalyticsSettings, + ILocalizationService localizationService) + { + _settingService = settingService; + _googleAnalyticsSettings = googleAnalyticsSettings; + _localizationService = localizationService; + } + + /// + /// Gets widget zones where this widget should be rendered + /// + /// Widget zones + public IList GetWidgetZones() + { + var zones = new List { "head_html_tag" }; + if (!string.IsNullOrWhiteSpace(_googleAnalyticsSettings.WidgetZone)) + { + zones = new List + { + _googleAnalyticsSettings.WidgetZone + }; + } + + return zones; + } + + /// + /// Gets a route for provider configuration + /// + /// Action name + /// Controller name + /// Route values + public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "Configure"; + controllerName = "WidgetsGoogleAnalytics"; routeValues = new RouteValueDictionary() { { "area", "SmartStore.GoogleAnalytics" } }; - } - - /// - /// Gets a route for displaying widget - /// - /// Widget zone where it's displayed - /// Action name - /// Controller name - /// Route values + } + + /// + /// Gets a route for displaying widget + /// + /// Widget zone where it's displayed + /// Action name + /// Controller name + /// Route values public void GetDisplayWidgetRoute(string widgetZone, object model, int storeId, out string actionName, out string controllerName, out RouteValueDictionary routeValues) - { - actionName = "PublicInfo"; - controllerName = "WidgetsGoogleAnalytics"; - routeValues = new RouteValueDictionary() - { - {"area", "SmartStore.GoogleAnalytics"}, - {"widgetZone", widgetZone} - }; - } - - /// - /// Install plugin - /// - public override void Install() - { - var settings = new GoogleAnalyticsSettings() - { - GoogleId = "UA-0000000-0", - TrackingScript = @" - ", - EcommerceScript = @" - ga('require', 'ecommerce'); - - ga('ecommerce:addTransaction', { - 'id': '{ORDERID}', - 'affiliation': '{SITE}', - 'revenue': '{TOTAL}', - 'shipping': '{SHIP}', - 'tax': '{TAX}', - 'currency': '{CURRENCY}' - }); - - {DETAILS} - - ga('ecommerce:send'); - ", - EcommerceDetailScript = @" - ga('ecommerce:addItem', { - 'id': '{ORDERID}', - 'name': '{PRODUCTNAME}', - 'sku': '{PRODUCTSKU}', - 'category': '{CATEGORYNAME}', - 'price': '{UNITPRICE}', - 'quantity': '{QUANTITY}' - }); - ", - - }; - _settingService.SaveSetting(settings); - - _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); - - base.Install(); - } - - /// - /// Uninstall plugin - /// - public override void Uninstall() - { - //locales - _localizationService.DeleteLocaleStringResources(this.PluginDescriptor.ResourceRootKey); - _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.Widgets.GoogleAnalytics", false); - + { + actionName = "PublicInfo"; + controllerName = "WidgetsGoogleAnalytics"; + routeValues = new RouteValueDictionary() + { + {"area", "SmartStore.GoogleAnalytics"}, + {"widgetZone", widgetZone} + }; + } + + public override void Install() + { + var settings = new GoogleAnalyticsSettings + { + GoogleId = "UA-0000000-0", + TrackingScript = TRACKING_SCRIPT, + EcommerceScript = ECOMMERCE_SCRIPT, + EcommerceDetailScript = ECOMMERCE_DETAIL_SCRIPT + }; + + _settingService.SaveSetting(settings); + _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); + + base.Install(); + } + + public override void Uninstall() + { + _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); + _localizationService.DeleteLocaleStringResources("Plugins.FriendlyName.Widgets.GoogleAnalytics", false); _settingService.DeleteSetting(); - - base.Uninstall(); - } - } + + base.Uninstall(); + } + } } diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml index d7f6055f92..40ef8fd50d 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml @@ -14,6 +14,10 @@
  • Klicken Sie den Speichern-Button.
  • Aktivieren Sie das Google Analytics Widget unter CMS > Widgets und Google Analytics wird in Ihre Webseite integriert.
  • +

    + Um das Opt-Out-Cookie in Ihrer Datenschutzerklärung einzubinden verwenden Sie folgenden Code: +

    +
    <a href="javascript:gaOptout()">Google Analytics deaktivieren</a>
    ]]> @@ -41,4 +45,7 @@ Kopieren Sie den von Google erzeugten Tracking-Code in dieses Feld. {ORDERID}, {PRODUCTSKU}, {PRODUCTNAME}, {CATEGORYNAME}, {UNITPRICE} und {QUANTITY} werden automatisch ersetzt. + + Das Tracking ist jetzt deaktiviert +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml index d54877d501..17f7386277 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml @@ -14,6 +14,10 @@
  • Click the Save button below.
  • Activate the Google Analytics widget under CMS > Widgets to integrate Google Analytics into your store.
  • +

    + To include the opt-out cookie in your privacy policy, use the following code: +

    +
    <a href="javascript:gaOptout()">Deactivate Google Analytics</a>
    ]]> @@ -41,4 +45,7 @@ Paste the tracking code generated by Google analytics here. {ORDERID}, {PRODUCTSKU}, {PRODUCTNAME}, {CATEGORYNAME}, {UNITPRICE}, {QUANTITY} will be dynamically replaced. + + Tracking is now disabled +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml b/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml index 6831c30136..75dda5169e 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Views/WidgetsGoogleAnalytics/Configure.cshtml @@ -42,8 +42,8 @@ @Html.SmartLabelFor(model => model.TrackingScript) - @Html.SettingOverrideCheckbox(model => model.TrackingScript) - @Html.TextAreaFor(model => model.TrackingScript, new { style = "height: 200px;" }) + @Html.SettingEditorFor(model => model.TrackingScript, + Html.TextAreaFor(model => model.TrackingScript, new { style = "height: 300px;" })) @Html.ValidationMessageFor(model => model.TrackingScript) @@ -52,8 +52,8 @@ @Html.SmartLabelFor(model => model.EcommerceScript) - @Html.SettingOverrideCheckbox(model => model.EcommerceScript) - @Html.TextAreaFor(model => model.EcommerceScript, new { style = "height: 50px;" }) + @Html.SettingEditorFor(model => model.EcommerceScript, + Html.TextAreaFor(model => model.EcommerceScript, new { style = "height: 300px;" })) @Html.ValidationMessageFor(model => model.EcommerceScript) @@ -62,8 +62,8 @@ @Html.SmartLabelFor(model => model.EcommerceDetailScript) - @Html.SettingOverrideCheckbox(model => model.EcommerceDetailScript) - @Html.TextAreaFor(model => model.EcommerceDetailScript, new { style = "height: 200px;" }) + @Html.SettingEditorFor(model => model.EcommerceDetailScript, + Html.TextAreaFor(model => model.EcommerceDetailScript, new { style = "height: 200px;" })) @Html.ValidationMessageFor(model => model.EcommerceDetailScript) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index ba5ab138af..1a363bf017 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.GoogleMerchantCenter.dll diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs index 3808bca487..a930d8907c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -2,8 +2,10 @@ using System.Web.Routing; using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Data.Migrations; +using SmartStore.GoogleMerchantCenter.Providers; using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Services; +using SmartStore.Services.DataExchange.Export; namespace SmartStore.GoogleMerchantCenter { @@ -11,13 +13,16 @@ public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable { private readonly IGoogleFeedService _googleFeedService; private readonly ICommonServices _services; + private readonly IExportProfileService _exportProfileService; public GoogleMerchantCenterFeedPlugin( IGoogleFeedService googleFeedService, - ICommonServices services) + ICommonServices services, + IExportProfileService exportProfileService) { _googleFeedService = googleFeedService; _services = services; + _exportProfileService = exportProfileService; } public static string SystemName @@ -35,7 +40,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "FeedGoogleMerchantCenter"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; + routeValues = new RouteValueDictionary { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; } /// @@ -55,7 +60,11 @@ public override void Uninstall() { _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); - var migrator = new DbMigrator(new Configuration()); + // Delete existing export profiles. + var profiles = _exportProfileService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName); + profiles.Each(x => _exportProfileService.DeleteExportProfile(x, true)); + + var migrator = new DbMigrator(new Configuration()); migrator.Update(DbMigrator.InitialDatabase); base.Uninstall(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index e7cd9117ac..1940907121 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -15,13 +15,21 @@ -
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • -
  • Standard Steuer- und Versanddaten sind in den Einstellungen Ihres Google-Merchant-Center-Kontos zu hinterlegen.
  • -
  • Mehr Informationen zu benötigten Feldern finden Sie im Artikel Produkt-Feed-Spezifikationen.
  • -
  • Eine Liste mit allen gültigen Google-Produkt-Kategorie finden Sie hier.
  • -]]> +
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • +
  • Standard Steuer- und Versanddaten sind in den Einstellungen Ihres Google-Merchant-Center-Kontos zu hinterlegen.
  • +
  • Mehr Informationen zu benötigten Feldern finden Sie im Artikel Produkt-Feed-Spezifikationen.
  • +
  • Eine Liste mit allen gültigen Google-Produkt-Kategorie finden Sie hier.
  • + ]]>
    + + + Produktdaten + + + Export-Profile + + Allgemeine Einstellungen @@ -169,9 +177,6 @@ Anzahl der Tage, nach dem die Artikel verfallen bzw. nicht mehr angezeigt werden sollen. Der Wert 0 bewirkt, dass kein Verfallsdatum exportiert wird. - - Der Wert muss zwischen 0 und 29 Tagen liegen. - Versanddaten exportieren diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index ca7f01c90a..f8cffa44f3 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -15,13 +15,20 @@ -
  • Either the GTIN (such as UPC, EAN, etc.) or manufacturer and manufacturer part number (MPN) are required for product identification. Google recommends that all three pieces of information be specified.
  • -
  • Specify default tax and shipping values in your Google Merchant Center account settings.
  • -
  • In order to get more info about required fields look at the following article Product feed specification.
  • -
  • You can find a list of all Google categories here.
  • -]]> +
  • Either the GTIN (such as UPC, EAN, etc.) or manufacturer and manufacturer part number (MPN) are required for product identification. Google recommends that all three pieces of information be specified.
  • +
  • Specify default tax and shipping values in your Google Merchant Center account settings.
  • +
  • In order to get more info about required fields look at the following article Product feed specification.
  • +
  • You can find a list of all Google categories here.
  • + ]]>
    + + + Product data + + + Export profiles + General settings @@ -169,9 +176,6 @@ Number of days after products should expire or no longer appear. A value of 0 causes that no expiry date will be exported. - - The value must be between 0 and 29 days. - Export shipping data diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs index 8213e76f08..828defcb7d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -1,14 +1,14 @@ -using System; +using FluentValidation; +using FluentValidation.Attributes; +using SmartStore.Web.Framework; +using System; using System.Collections.Generic; using System.Web.Mvc; using System.Xml.Serialization; -using FluentValidation.Attributes; -using SmartStore.GoogleMerchantCenter.Validators; -using SmartStore.Web.Framework; namespace SmartStore.GoogleMerchantCenter.Models { - [Serializable] + [Serializable] [Validator(typeof(ProfileConfigurationValidator))] public class ProfileConfigurationModel { @@ -67,4 +67,12 @@ public ProfileConfigurationModel() [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] public bool ExportBasePrice { get; set; } } + + public class ProfileConfigurationValidator : AbstractValidator + { + public ProfileConfigurationValidator() + { + RuleFor(x => x.ExpirationDays).InclusiveBetween(0, 29); + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index c4c2eed881..84c27f6cf7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Web.Mvc; using System.Xml; +using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; @@ -15,6 +16,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.DataExchange.Export; using SmartStore.Services.Directory; +using SmartStore.Services.Localization; namespace SmartStore.GoogleMerchantCenter.Providers { @@ -28,7 +30,6 @@ namespace SmartStore.GoogleMerchantCenter.Providers ExportFeatures.CanProjectDescription | ExportFeatures.UsesSkuAsMpnFallback | ExportFeatures.OffersBrandFallback | - ExportFeatures.CanIncludeMainPicture | ExportFeatures.UsesSpecialPrice | ExportFeatures.UsesAttributeCombination)] public class GmcXmlExportProvider : ExportProviderBase @@ -38,17 +39,21 @@ public class GmcXmlExportProvider : ExportProviderBase private readonly IGoogleFeedService _googleFeedService; private readonly IMeasureService _measureService; private readonly ICommonServices _services; + private readonly IProductAttributeService _productAttributeService; private readonly MeasureSettings _measureSettings; + private Multimap _attributeMappings; public GmcXmlExportProvider( IGoogleFeedService googleFeedService, IMeasureService measureService, ICommonServices services, + IProductAttributeService productAttributeService, MeasureSettings measureSettings) { _googleFeedService = googleFeedService; _measureService = measureService; _services = services; + _productAttributeService = productAttributeService; _measureSettings = measureSettings; T = NullLocalizer.Instance; @@ -56,6 +61,19 @@ public GmcXmlExportProvider( public Localizer T { get; set; } + private Multimap AttributeMappings + { + get + { + if (_attributeMappings == null) + { + _attributeMappings = _productAttributeService.GetExportFieldMappings("gmc"); + } + + return _attributeMappings; + } + } + private string BasePriceUnits(string value) { const string defaultValue = "kg"; @@ -136,29 +154,68 @@ private void WriteString(XmlWriter writer, string fieldName, string value) } } - private void WriteString( - XmlWriter writer, - Dictionary mappedValues, + private string GetAttributeValue( + Multimap attributeValues, string fieldName, - string value) + int languageId, + string productEditTabValue, + string defaultValue) { - if (mappedValues == null) + // 1. attribute export mapping. + if (attributeValues != null && AttributeMappings.ContainsKey(fieldName)) { - // Regular product. - WriteString(writer, fieldName, value); - } - else - { - // Export attribute combination. - if (mappedValues.ContainsKey(fieldName)) + foreach (var attributeId in AttributeMappings[fieldName]) { - WriteString(writer, fieldName, mappedValues[fieldName].EmptyNull()); - } - else - { - WriteString(writer, fieldName, value); + if (attributeValues.ContainsKey(attributeId)) + { + var attributeValue = attributeValues[attributeId].FirstOrDefault(x => x.ProductVariantAttribute.ProductAttributeId == attributeId); + if (attributeValue != null) + { + return attributeValue.GetLocalized(x => x.Name, languageId, true, false).Value.EmptyNull(); + } + } } } + + // 2. explicit set to unspecified. + if (defaultValue.IsCaseInsensitiveEqual(Unspecified)) + { + return string.Empty; + } + + // 3. product edit tab value. + if (productEditTabValue.HasValue()) + { + return productEditTabValue; + } + + return defaultValue.EmptyNull(); + } + + private string GetBaseMeasureWeight() + { + var measureWeightEntity = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var measureWeight = measureWeightEntity != null + ? measureWeightEntity.SystemKeyword.EmptyNull().ToLower() + : string.Empty; + + switch (measureWeight) + { + case "gram": + case "gramme": + return "g"; + case "mg": + case "milligramme": + case "milligram": + return "mg"; + case "lb": + return "lb"; + case "ounce": + case "oz": + return "oz"; + default: + return "kg"; + } } public static string SystemName => "Feeds.GoogleMerchantCenterProductXml"; @@ -192,16 +249,32 @@ public override ExportConfigurationInfo ConfigurationInfo protected override void Export(ExportExecuteContext context) { Currency currency = context.Currency.Entity; - var languageId = (int)context.Language.Id; - var measureWeightSystemKey = ""; + var languageId = context.Projection.LanguageId ?? 0; var dateFormat = "yyyy-MM-ddTHH:mmZ"; + var defaultCondition = "new"; + var defaultAvailability = "in stock"; + var measureWeight = GetBaseMeasureWeight(); - var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); - if (measureWeight != null) - measureWeightSystemKey = measureWeight.SystemKeyword; + if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + { + defaultCondition = string.Empty; + } + else if (config.Condition.HasValue()) + { + defaultCondition = config.Condition; + } + + if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) + { + defaultAvailability = string.Empty; + } + else if (config.Availability.HasValue()) + { + defaultAvailability = config.Availability; + } - var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); using (var writer = XmlWriter.Create(context.DataStream, ExportXmlHelper.DefaultSettings)) { @@ -238,17 +311,22 @@ protected override void Export(ExportExecuteContext context) { string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; - string mainImageUrl = product._MainPictureUrl; var price = (decimal)product.Price; var uniqueId = (string)product._UniqueId; + var isParent = (bool)product._IsParent; string brand = product._Brand; string gtin = product.Gtin; string mpn = product.ManufacturerPartNumber; - string condition = "new"; - string availability = "in stock"; + var availability = defaultAvailability; + List productPictures = product.ProductPictures; + var pictureUrls = productPictures + .Select(x => (string)x.Picture._FullSizeImageUrl) + .Where(x => x.HasValue()) + .ToList(); - var combinationValues = product._AttributeCombinationValues as ICollection; - var mappedValues = (combinationValues != null ? combinationValues.GetMappedValuesFromAlias("gmc", languageId) : null); + var attributeValues = !isParent && product._AttributeCombinationValues != null + ? ((ICollection)product._AttributeCombinationValues).ToMultimap(x => x.ProductVariantAttribute.ProductAttributeId, x => x) + : new Multimap(); var specialPrice = product._FutureSpecialPrice as decimal?; if (!specialPrice.HasValue) @@ -260,32 +338,12 @@ protected override void Export(ExportExecuteContext context) if (category.IsEmpty()) context.Log.Error(T("Plugins.Feed.Froogle.MissingDefaultCategory")); - if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + if (entity.ManageInventoryMethod == ManageInventoryMethod.ManageStock && entity.StockQuantity <= 0) { - condition = ""; - } - else if (config.Condition.HasValue()) - { - condition = config.Condition; - } - - if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) - { - availability = ""; - } - else if (config.Availability.HasValue()) - { - availability = config.Availability; - } - else - { - if (entity.ManageInventoryMethod == ManageInventoryMethod.ManageStock && entity.StockQuantity <= 0) - { - if (entity.BackorderMode == BackorderMode.NoBackorders) - availability = "out of stock"; - else if (entity.BackorderMode == BackorderMode.AllowQtyBelow0 || entity.BackorderMode == BackorderMode.AllowQtyBelow0AndNotifyCustomer) - availability = (entity.AvailableForPreOrder ? "preorder" : "out of stock"); - } + if (entity.BackorderMode == BackorderMode.NoBackorders) + availability = "out of stock"; + else if (entity.BackorderMode == BackorderMode.AllowQtyBelow0 || entity.BackorderMode == BackorderMode.AllowQtyBelow0AndNotifyCustomer) + availability = entity.AvailableForPreOrder ? "preorder" : "out of stock"; } WriteString(writer, "id", uniqueId); @@ -311,25 +369,26 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("link", (string)product._DetailUrl); - if (mainImageUrl.HasValue()) + if (pictureUrls.Any()) { - WriteString(writer, "image_link", mainImageUrl); - } + WriteString(writer, "image_link", pictureUrls.First()); - if (config.AdditionalImages) - { - var imageCount = 0; - foreach (dynamic productPicture in product.ProductPictures) + if (config.AdditionalImages) { - string pictureUrl = productPicture.Picture._ImageUrl; - if (pictureUrl.HasValue() && (mainImageUrl.IsEmpty() || !mainImageUrl.IsCaseInsensitiveEqual(pictureUrl)) && ++imageCount <= 10) + var imageCount = 0; + foreach (var url in pictureUrls.Skip(1)) { - WriteString(writer, "additional_image_link", pictureUrl); + if (++imageCount <= 10) + { + WriteString(writer, "additional_image_link", url); + } } } } + var condition = GetAttributeValue(attributeValues, "condition", languageId, null, defaultCondition); WriteString(writer, "condition", condition); + WriteString(writer, "availability", availability); if (availability == "preorder" && entity.AvailableStartDateTimeUtc.HasValue && entity.AvailableStartDateTimeUtc.Value > DateTime.UtcNow) @@ -363,21 +422,29 @@ protected override void Export(ExportExecuteContext context) var identifierExists = brand.HasValue() && (gtin.HasValue() || mpn.HasValue()); WriteString(writer, "identifier_exists", identifierExists ? "yes" : "no"); - if (config.Gender.IsCaseInsensitiveEqual(Unspecified)) - WriteString(writer, "gender", ""); - else - WriteString(writer, "gender", gmc != null && gmc.Gender.HasValue() ? gmc.Gender : config.Gender); + var gender = GetAttributeValue(attributeValues, "gender", languageId, gmc?.Gender, config.Gender); + WriteString(writer, "gender", gender); - if (config.AgeGroup.IsCaseInsensitiveEqual(Unspecified)) - WriteString(writer, "age_group", ""); - else - WriteString(writer, "age_group", gmc != null && gmc.AgeGroup.HasValue() ? gmc.AgeGroup : config.AgeGroup); + var ageGroup = GetAttributeValue(attributeValues, "age_group", languageId, gmc?.AgeGroup, config.AgeGroup); + WriteString(writer, "age_group", ageGroup); - WriteString(writer, "color", gmc != null && gmc.Color.HasValue() ? gmc.Color : config.Color); - WriteString(writer, "size", gmc != null && gmc.Size.HasValue() ? gmc.Size : config.Size); - WriteString(writer, "material", gmc != null && gmc.Material.HasValue() ? gmc.Material : config.Material); - WriteString(writer, "pattern", gmc != null && gmc.Pattern.HasValue() ? gmc.Pattern : config.Pattern); - WriteString(writer, "item_group_id", gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : ""); + var color = GetAttributeValue(attributeValues, "color", languageId, gmc?.Color, config.Color); + WriteString(writer, "color", color); + + var size = GetAttributeValue(attributeValues, "size", languageId, gmc?.Size, config.Size); + WriteString(writer, "size", size); + + var material = GetAttributeValue(attributeValues, "material", languageId, gmc?.Material, config.Material); + WriteString(writer, "material", material); + + var pattern = GetAttributeValue(attributeValues, "pattern", languageId, gmc?.Pattern, config.Pattern); + WriteString(writer, "pattern", pattern); + + var itemGroupId = gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : string.Empty; + if (itemGroupId.HasValue()) + { + WriteString(writer, "item_group_id", itemGroupId); + } if (config.ExpirationDays > 0) { @@ -386,19 +453,8 @@ protected override void Export(ExportExecuteContext context) if (config.ExportShipping) { - string weightInfo; - var weight = ((decimal)product.Weight).FormatInvariant(); - - if (measureWeightSystemKey.IsCaseInsensitiveEqual("gram")) - weightInfo = weight + " g"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("lb")) - weightInfo = weight + " lb"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("ounce")) - weightInfo = weight + " oz"; - else - weightInfo = weight + " kg"; - - WriteString(writer, "shipping_weight", weightInfo); + var weight = string.Concat(((decimal)product.Weight).FormatInvariant(), " ", measureWeight); + WriteString(writer, "shipping_weight", weight); } if (config.ExportBasePrice && entity.BasePriceHasValue) @@ -421,14 +477,14 @@ protected override void Export(ExportExecuteContext context) WriteString(writer, "is_bundle", gmc.IsBundle.HasValue ? (gmc.IsBundle.Value ? "yes" : "no") : null); WriteString(writer, "adult", gmc.IsAdult.HasValue ? (gmc.IsAdult.Value ? "yes" : "no") : null); WriteString(writer, "energy_efficiency_class", gmc.EnergyEfficiencyClass.HasValue() ? gmc.EnergyEfficiencyClass : null); - - WriteString(writer, "custom_label_0", gmc.CustomLabel0.HasValue() ? gmc.CustomLabel0 : null); - WriteString(writer, "custom_label_1", gmc.CustomLabel1.HasValue() ? gmc.CustomLabel1 : null); - WriteString(writer, "custom_label_2", gmc.CustomLabel2.HasValue() ? gmc.CustomLabel2 : null); - WriteString(writer, "custom_label_3", gmc.CustomLabel3.HasValue() ? gmc.CustomLabel3 : null); - WriteString(writer, "custom_label_4", gmc.CustomLabel4.HasValue() ? gmc.CustomLabel4 : null); } + var customLabel0 = GetAttributeValue(attributeValues, "custom_label_0", languageId, gmc?.CustomLabel0, null); + var customLabel1 = GetAttributeValue(attributeValues, "custom_label_1", languageId, gmc?.CustomLabel1, null); + var customLabel2 = GetAttributeValue(attributeValues, "custom_label_2", languageId, gmc?.CustomLabel2, null); + var customLabel3 = GetAttributeValue(attributeValues, "custom_label_3", languageId, gmc?.CustomLabel3, null); + var customLabel4 = GetAttributeValue(attributeValues, "custom_label_4", languageId, gmc?.CustomLabel4, null); + ++context.RecordsSucceeded; } catch (Exception exception) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index 3fbb85e2ab..dcd34d683c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -99,8 +99,8 @@ ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll False - - ..\..\packages\FluentValidation.6.4.1\lib\Net45\FluentValidation.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -193,7 +193,6 @@ - diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs deleted file mode 100644 index 9799ada57f..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentValidation; -using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.Services.Localization; - -namespace SmartStore.GoogleMerchantCenter.Validators -{ - public class ProfileConfigurationValidator : AbstractValidator - { - public ProfileConfigurationValidator(ILocalizationService localize) - { - RuleFor(x => x.ExpirationDays).InclusiveBetween(0, 29) - .WithMessage(localize.GetResource("Plugins.Feed.Froogle.ExpirationDays.Validate")); - } - } -} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 261d2597f4..7f047f12de 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -8,10 +8,25 @@ @{ Layout = ""; - Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); + Html.AddCssFileParts(true, "~/Content/vendors/x-editable/bootstrap-editable.css"); + Html.AppendScriptParts(true, "~/Content/vendors/x-editable/bootstrap-editable.js"); } + +
    @@ -26,121 +41,172 @@
    -
    - @{ Html.RenderAction("InfoProfile", "Export", new { systemName = GmcXmlExportProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" }); } - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - @(Html.Telerik().Grid() - .Name("gmc-products-grid") - .DataKeys(keys => - { - keys.Add(x => x.ProductId).RouteKey("ProductId"); - }) - .Columns(c => - { - c.Bound(x => x.ProductId) - .ReadOnly() - .Visible(false); - c.Bound(x => x.Name) - .ReadOnly().Visible(true) - .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); - c.Bound(x => x.SKU) - .ReadOnly() - .Visible(true); - c.Bound(x => x.Export2) - .ClientTemplate(XEditableLink("Export2", "select2").ToHtmlString()); - c.Bound(x => x.Taxonomy) - .ClientTemplate(XEditableLink("Taxonomy", "select2").ToHtmlString()); - c.Bound(x => x.Gender) - .ClientTemplate(XEditableLink("Gender", "select2").ToHtmlString()); - c.Bound(x => x.AgeGroup) - .ClientTemplate(XEditableLink("AgeGroup", "select2").ToHtmlString()); - c.Bound(x => x.IsAdult) - .ClientTemplate(XEditableLink("IsAdult", "select2").ToHtmlString()); - c.Bound(x => x.Color) - .ClientTemplate(XEditableLink("Color", "text").ToHtmlString()); - c.Bound(x => x.Size) - .ClientTemplate(XEditableLink("Size", "text").ToHtmlString()); - c.Bound(x => x.Material) - .ClientTemplate(XEditableLink("Material", "text").ToHtmlString()); - c.Bound(x => x.Pattern) - .ClientTemplate(XEditableLink("Pattern", "text").ToHtmlString()); - c.Bound(x => x.Multipack2) - .ClientTemplate(XEditableLink("Multipack2", "text").ToHtmlString()); - c.Bound(x => x.IsBundle) - .ClientTemplate(XEditableLink("IsBundle", "select2").ToHtmlString()); - c.Bound(x => x.EnergyEfficiencyClass) - .ClientTemplate(XEditableLink("EnergyEfficiencyClass", "select2").ToHtmlString()); - c.Bound(x => x.CustomLabel0) - .ClientTemplate(XEditableLink("CustomLabel0", "text").ToHtmlString()); - c.Bound(x => x.CustomLabel1) - .ClientTemplate(XEditableLink("CustomLabel1", "text").ToHtmlString()); - c.Bound(x => x.CustomLabel2) - .ClientTemplate(XEditableLink("CustomLabel2", "text").ToHtmlString()); - c.Bound(x => x.CustomLabel3) - .ClientTemplate(XEditableLink("CustomLabel3", "text").ToHtmlString()); - c.Bound(x => x.CustomLabel4) - .ClientTemplate(XEditableLink("CustomLabel4", "text").ToHtmlString()); - }) - .ClientEvents(e => - { - e.OnDataBound("OnGridDataBound"); - e.OnDataBinding("OnGridDataBinding"); - e.OnError("OnGridError"); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .PreserveGridState() - .EnableCustomBinding(true) - ) -
    -
    + +@Html.SmartStore().TabStrip().Name("gmc-config").Style(TabsStyle.Material).Items(x => +{ + x.Add().Text(T("Plugins.Feed.Froogle.TabTitleGrid").Text).Content(TabGrid()).Selected(true); + x.Add().Text(T("Plugins.Feed.Froogle.TabTitleProfiles").Text).Content(TabProfiles()); +}) + + +@helper TabGrid() +{ +
    +
    + @Html.SmartLabelFor(model => model.SearchProductName) + @Html.EditorFor(model => Model.SearchProductName, new { @class = "form-control" }) +
    +
    + @Html.SmartLabelFor(m => m.SearchIsTouched) + @Html.DropDownListFor(m => m.SearchIsTouched, new List + { + new SelectListItem { Text = T("Common.Unspecified"), Value = "" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Touched"), Value = "touched" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Untouched"), Value = "untouched" } + }) +
    +
    + + +
    +
    + + +
    +
    + +
    + @(Html.Telerik().Grid() + .Name("gmc-products-grid") + .DataKeys(keys => + { + keys.Add(x => x.ProductId).RouteKey("ProductId"); + }) + .Columns(c => + { + c.Bound(x => x.ProductId) + .ReadOnly() + .Visible(false); + c.Bound(x => x.Name) + .ReadOnly().Visible(true) + //.Width("30%") + .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) + .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); + c.Bound(x => x.SKU) + //.Width(100) + .ReadOnly() + .Visible(true); + c.Bound(x => x.Export2) + .Width(100) + .ClientTemplate(XEditableLink("Export2", "select2").ToHtmlString()); + c.Bound(x => x.Taxonomy) + .HtmlAttributes(new { @class = "gmc-taxonomy" }) + .HeaderHtmlAttributes(new { @class = "gmc-taxonomy", data_field = "taxonomy" }) + .ClientTemplate(XEditableLink("Taxonomy", "select2").ToHtmlString()); + c.Bound(x => x.Gender) + .HtmlAttributes(new { @class = "gmc-gender" }) + .HeaderHtmlAttributes(new { @class = "gmc-gender", data_field = "gender" }) + .ClientTemplate(XEditableLink("Gender", "select2").ToHtmlString()); + c.Bound(x => x.AgeGroup) + .HtmlAttributes(new { @class = "gmc-agegroup" }) + .HeaderHtmlAttributes(new { @class = "gmc-agegroup", data_field = "agegroup" }) + .ClientTemplate(XEditableLink("AgeGroup", "select2").ToHtmlString()); + c.Bound(x => x.IsAdult) + .HtmlAttributes(new { @class = "gmc-adult" }) + .HeaderHtmlAttributes(new { @class = "gmc-adult", data_field = "adult" }) + .ClientTemplate(XEditableLink("IsAdult", "select2").ToHtmlString()); + c.Bound(x => x.Color) + .HtmlAttributes(new { @class = "gmc-color" }) + .HeaderHtmlAttributes(new { @class = "gmc-color", data_field = "color" }) + .ClientTemplate(XEditableLink("Color", "text").ToHtmlString()); + c.Bound(x => x.Size) + .HtmlAttributes(new { @class = "gmc-size" }) + .HeaderHtmlAttributes(new { @class = "gmc-size", data_field = "size" }) + .ClientTemplate(XEditableLink("Size", "text").ToHtmlString()); + c.Bound(x => x.Material) + .HtmlAttributes(new { @class = "gmc-material" }) + .HeaderHtmlAttributes(new { @class = "gmc-material", data_field = "material" }) + .ClientTemplate(XEditableLink("Material", "text").ToHtmlString()); + c.Bound(x => x.Pattern) + .HtmlAttributes(new { @class = "gmc-pattern" }) + .HeaderHtmlAttributes(new { @class = "gmc-pattern", data_field = "pattern" }) + .ClientTemplate(XEditableLink("Pattern", "text").ToHtmlString()); + c.Bound(x => x.Multipack2) + .HtmlAttributes(new { @class = "gmc-multipack" }) + .HeaderHtmlAttributes(new { @class = "gmc-multipack", data_field = "multipack" }) + .ClientTemplate(XEditableLink("Multipack2", "text").ToHtmlString()); + c.Bound(x => x.IsBundle) + .HtmlAttributes(new { @class = "gmc-bundle" }) + .HeaderHtmlAttributes(new { @class = "gmc-bundle", data_field = "bundle" }) + .ClientTemplate(XEditableLink("IsBundle", "select2").ToHtmlString()); + c.Bound(x => x.EnergyEfficiencyClass) + .HtmlAttributes(new { @class = "gmc-eec" }) + .HeaderHtmlAttributes(new { @class = "gmc-eec", data_field = "eec" }) + .ClientTemplate(XEditableLink("EnergyEfficiencyClass", "select2").ToHtmlString()); + c.Bound(x => x.CustomLabel0) + .HtmlAttributes(new { @class = "gmc-label0" }) + .HeaderHtmlAttributes(new { @class = "gmc-label0", data_field = "label0" }) + .ClientTemplate(XEditableLink("CustomLabel0", "text").ToHtmlString()); + c.Bound(x => x.CustomLabel1) + .HtmlAttributes(new { @class = "gmc-label1" }) + .HeaderHtmlAttributes(new { @class = "gmc-label1", data_field = "label1" }) + .ClientTemplate(XEditableLink("CustomLabel1", "text").ToHtmlString()); + c.Bound(x => x.CustomLabel2) + .HtmlAttributes(new { @class = "gmc-label2" }) + .HeaderHtmlAttributes(new { @class = "gmc-label2", data_field = "label2" }) + .ClientTemplate(XEditableLink("CustomLabel2", "text").ToHtmlString()); + c.Bound(x => x.CustomLabel3) + .HtmlAttributes(new { @class = "gmc-label3" }) + .HeaderHtmlAttributes(new { @class = "gmc-label3", data_field = "label3" }) + .ClientTemplate(XEditableLink("CustomLabel3", "text").ToHtmlString()); + c.Bound(x => x.CustomLabel4) + .HtmlAttributes(new { @class = "gmc-label4" }) + .HeaderHtmlAttributes(new { @class = "gmc-label4", data_field = "label4" }) + .ClientTemplate(XEditableLink("CustomLabel4", "text").ToHtmlString()); + }) + .ClientEvents(e => + { + e.OnDataBinding("OnGridDataBinding"); + e.OnDataBound("OnGridDataBound"); + e.OnRowDataBound("OnGridRowDataBound"); + e.OnError("OnGridError"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true) + ) + +
    +} + +@helper TabProfiles() +{ + + + @Html.Action("InfoProfile", "Export", new { systemName = GmcXmlExportProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" }) +} @helper XEditableLink(string fieldName, string type) { @@ -157,15 +223,81 @@ data-type="@type">@displayText } - \ No newline at end of file + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config index 3c918560a7..b3f23c4249 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config @@ -3,7 +3,7 @@ - + diff --git a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs index 19d86f190d..3bbdb6c1d7 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Controllers/OfflinePaymentController.cs @@ -1,35 +1,39 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using Autofac; +using Autofac; using FluentValidation; using FluentValidation.Results; using SmartStore.OfflinePayment.Models; using SmartStore.OfflinePayment.Settings; -using SmartStore.OfflinePayment.Validators; using SmartStore.Services; +using SmartStore.Services.Media; using SmartStore.Services.Payments; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using SmartStore.Web.Framework.Theming; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; namespace SmartStore.OfflinePayment.Controllers { - public class OfflinePaymentController : PaymentControllerBase + public class OfflinePaymentController : PaymentControllerBase { private readonly IComponentContext _ctx; private readonly HttpContextBase _httpContext; + private readonly IPictureService _pictureService; public OfflinePaymentController( HttpContextBase httpContext, - IComponentContext ctx) + IComponentContext ctx, + IPictureService pictureService) { _httpContext = httpContext; _ctx = ctx; - } + _pictureService = pictureService; + } #region Global @@ -52,10 +56,15 @@ private TModel ConfigureGet(Action fn = null { var model = new TModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); + var store = storeScope == 0 + ? Services.StoreContext.CurrentStore + : Services.StoreService.GetStoreById(storeScope); + model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; model.DescriptionText = settings.DescriptionText; + model.PaymentMethodLogo = settings.ThumbnailPictureId; model.AdditionalFee = settings.AdditionalFee; model.AdditionalFeePercentage = settings.AdditionalFeePercentage; @@ -82,6 +91,7 @@ private void ConfigurePost(TModel model, FormCollection form, var settings = Services.Settings.LoadSetting(storeScope); settings.DescriptionText = model.DescriptionText; + settings.ThumbnailPictureId = model.PaymentMethodLogo; settings.AdditionalFee = model.AdditionalFee; settings.AdditionalFeePercentage = model.AdditionalFeePercentage; @@ -90,9 +100,12 @@ private void ConfigurePost(TModel model, FormCollection form, fn(settings); } - storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - NotifySuccess(Services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); } [NonAction] @@ -103,6 +116,7 @@ private TModel PaymentInfoGet(Action fn = nu var settings = _ctx.Resolve(); var model = new TModel(); model.DescriptionText = GetLocalizedText(settings.DescriptionText); + model.ThumbnailUrl = _pictureService.GetUrl(settings.ThumbnailPictureId, 120, false); if (fn != null) { @@ -135,7 +149,7 @@ public override IList ValidatePaymentForm(FormCollection form) { if (type == "Manual") { - validator = new ManualPaymentInfoValidator(Services.Localization); + validator = new ManualPaymentInfoValidator(T); var model = new ManualPaymentInfoModel { CardholderName = form["CardholderName"], @@ -146,7 +160,7 @@ public override IList ValidatePaymentForm(FormCollection form) } else if (type == "DirectDebit") { - validator = new DirectDebitPaymentInfoValidator(Services.Localization); + validator = new DirectDebitPaymentInfoValidator(); var model = new DirectDebitPaymentInfoModel { EnterIBAN = form["EnterIBAN"], @@ -250,11 +264,10 @@ public override string GetPaymentSummary(FormCollection form) } #endregion - - + #region CashOnDelivery - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult CashOnDeliveryConfigure() { var model = ConfigureGet(); @@ -262,7 +275,7 @@ public ActionResult CashOnDeliveryConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult CashOnDeliveryConfigure(CashOnDeliveryConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -280,11 +293,10 @@ public ActionResult CashOnDeliveryPaymentInfo() } #endregion - - + #region Invoice - [ChildActionOnly, AdminAuthorize] + [ChildActionOnly, AdminThemed, AdminAuthorize] public ActionResult InvoiceConfigure() { var model = ConfigureGet(); @@ -292,7 +304,7 @@ public ActionResult InvoiceConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult InvoiceConfigure(InvoiceConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -310,11 +322,10 @@ public ActionResult InvoicePaymentInfo() } #endregion - - + #region PayInStore - [ChildActionOnly, AdminAuthorize] + [ChildActionOnly, AdminThemed, AdminAuthorize] public ActionResult PayInStoreConfigure() { var model = ConfigureGet(); @@ -322,7 +333,7 @@ public ActionResult PayInStoreConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PayInStoreConfigure(PayInStoreConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -340,11 +351,10 @@ public ActionResult PayInStorePaymentInfo() } #endregion - - + #region Prepayment - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult PrepaymentConfigure() { var model = ConfigureGet(); @@ -352,7 +362,7 @@ public ActionResult PrepaymentConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PrepaymentConfigure(PrepaymentConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -370,11 +380,10 @@ public ActionResult PrepaymentPaymentInfo() } #endregion - - + #region DirectDebit - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult DirectDebitConfigure() { var model = ConfigureGet(); @@ -382,7 +391,7 @@ public ActionResult DirectDebitConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult DirectDebitConfigure(DirectDebitConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -410,11 +419,10 @@ public ActionResult DirectDebitPaymentInfo() } #endregion - - + #region Manual - [AdminAuthorize, ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult ManualConfigure() { var model = ConfigureGet((m, s) => @@ -436,7 +444,7 @@ public ActionResult ManualConfigure() return View(model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult ManualConfigure(ManualConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) @@ -512,8 +520,7 @@ public ActionResult ManualPaymentInfo() #region PurchaseOrderNumber - [AdminAuthorize] - [ChildActionOnly] + [AdminAuthorize, AdminThemed, ChildActionOnly] public ActionResult PurchaseOrderNumberConfigure() { var model = ConfigureGet(); @@ -521,13 +528,13 @@ public ActionResult PurchaseOrderNumberConfigure() return View("GenericConfigure", model); } - [HttpPost, AdminAuthorize, ChildActionOnly, ValidateInput(false)] + [HttpPost, AdminAuthorize, AdminThemed, ChildActionOnly, ValidateInput(false)] public ActionResult PurchaseOrderNumberConfigure(PurchaseOrderNumberConfigurationModel model, FormCollection form) { if (!ModelState.IsValid) - return InvoiceConfigure(); + return PurchaseOrderNumberConfigure(); - ConfigurePost(model, form); + ConfigurePost(model, form); return PurchaseOrderNumberConfigure(); } diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index b157e3017d..b03a02c439 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Description.txt +++ b/src/Plugins/SmartStore.OfflinePayment/Description.txt @@ -2,8 +2,8 @@ Description: Contains common offline payment methods like Direct Debit, Invoice, Prepayment etc. Group: Payment SystemName: SmartStore.OfflinePayment -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 0 FileName: SmartStore.OfflinePayment.dll ResourceRootKey: Plugins.SmartStore.OfflinePayment diff --git a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml index a9f2235ba1..fc3ede7e9b 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.de-de.xml @@ -13,17 +13,11 @@ Geben Sie hier die Beschreibung an, die dem Kunden im Bestellprozess angezeigt wird. - - Zusätzliche Gebühr + + Logo - - Bestimmt die Gebühr, die dem Kunden für die Nutzung dieser Zahlart berechnet wird. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Ein fester Wert wird verwendet, falls diese Option nicht aktiviert ist. + + Laden Sie hier eine Grafik hoch, welche auf der Zahlartauswahlseite als Thumbnail der Zahlart angezeigt wird. @@ -121,6 +115,13 @@ + + + + + Zahlungsstatus nach Bestellabschluss @@ -199,21 +200,6 @@ Ja - - Geben Sie bitte den Namen des Kontoinhabers ein. - - - Geben Sie bitte Ihre Kontonummer ein. - - - Geben Sie bitte die Bankleitzahl (BLZ) ein. - - - Geben Sie bitte die IBAN (International Bank Account Number) ein. - - - Geben Sie bitte die BIC (Bank Interchange Code) ein. - diff --git a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml index 5a47c5e0e1..0dcc2a5491 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.OfflinePayment/Localization/resources.en-us.xml @@ -13,17 +13,11 @@ Enter info that will be shown to customers during checkout. - - Additional fee + + Logo - - Determines the additional fee. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. + + Upload an image which will be displayed as thumbnail of the payment method on the payment selection page. @@ -121,6 +115,13 @@ + + + + + Payment status after order completion @@ -202,21 +203,6 @@ Yes - - Please enter the name of the account holder. - - - Please enter your account number. - - - Please enter the bank code. - - - Please enter the IBAN (International Bank Account Number). - - - Please enter the BIC (Bank Interchange Code). - diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs index ba59935fdb..f69436a624 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/ConfigurationModel.cs @@ -3,21 +3,28 @@ using SmartStore.OfflinePayment.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using System.ComponentModel.DataAnnotations; namespace SmartStore.OfflinePayment.Models { public abstract class ConfigurationModelBase : ModelBase { + public string PrimaryStoreCurrencyCode { get; set; } + [AllowHtml] [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.DescriptionText")] public string DescriptionText { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.AdditionalFee")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.AdditionalFeePercentage")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } - } + + [SmartResourceDisplayName("Plugins.SmartStore.OfflinePayment.PaymentMethodLogo")] + [UIHint("Picture")] + public int PaymentMethodLogo { get; set; } + } public class CashOnDeliveryConfigurationModel : ConfigurationModelBase { diff --git a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs index b13dd98de0..6046a28975 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Models/PaymentInfoModel.cs @@ -2,13 +2,17 @@ using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using FluentValidation; +using SmartStore.Core.Localization; +using SmartStore.Web.Framework.Validators; namespace SmartStore.OfflinePayment.Models { public abstract class PaymentInfoModelBase : ModelBase { public string DescriptionText { get; set; } - } + public string ThumbnailUrl { get; set; } + } public class CashOnDeliveryPaymentInfoModel : PaymentInfoModelBase { @@ -103,4 +107,30 @@ public class PurchaseOrderNumberPaymentInfoModel : PaymentInfoModelBase [AllowHtml] public string PurchaseOrderNumber { get; set; } } + + #region validators + + public class DirectDebitPaymentInfoValidator : AbstractValidator + { + public DirectDebitPaymentInfoValidator() + { + RuleFor(x => x.DirectDebitAccountHolder).NotEmpty(); + RuleFor(x => x.DirectDebitAccountNumber).NotEmpty().When(x => x.EnterIBAN == "no-iban"); + RuleFor(x => x.DirectDebitBankCode).NotEmpty().When(x => x.EnterIBAN == "no-iban"); + RuleFor(x => x.DirectDebitIban).Matches(RegularExpressions.IsIban).When(x => x.EnterIBAN == "iban"); + RuleFor(x => x.DirectDebitBic).Matches(RegularExpressions.IsBic).When(x => x.EnterIBAN == "iban"); + } + } + + public class ManualPaymentInfoValidator : AbstractValidator + { + public ManualPaymentInfoValidator(Localizer T) + { + RuleFor(x => x.CardholderName).NotEmpty(); + RuleFor(x => x.CardNumber).CreditCard().WithMessage(T("Payment.CardNumber.Wrong")); + RuleFor(x => x.CardCode).CreditCardCvvNumber(); + } + } + + #endregion } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs index 52fbe9bc18..89c409a2b8 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Settings/OfflinePaymentSettings.cs @@ -6,6 +6,7 @@ namespace SmartStore.OfflinePayment.Settings public abstract class PaymentSettingsBase : ISettings { public string DescriptionText { get; set; } + public int ThumbnailPictureId { get; set; } public decimal AdditionalFee { get; set; } public bool AdditionalFeePercentage { get; set; } } diff --git a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj index 06fca7adc5..24bf962f53 100644 --- a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj +++ b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj @@ -43,6 +43,7 @@ + true @@ -85,8 +86,8 @@ ..\..\packages\Autofac.4.5.0\lib\net45\Autofac.dll - - ..\..\packages\FluentValidation.6.4.1\lib\Net45\FluentValidation.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -95,6 +96,7 @@ ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll + @@ -152,8 +154,6 @@ - - diff --git a/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs b/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs deleted file mode 100644 index 7698daebba..0000000000 --- a/src/Plugins/SmartStore.OfflinePayment/Validators/DirectDebitPaymentInfoValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -using FluentValidation; -using SmartStore.OfflinePayment.Models; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Validators; - -namespace SmartStore.OfflinePayment.Validators -{ - public class DirectDebitPaymentInfoValidator : AbstractValidator - { - public DirectDebitPaymentInfoValidator(ILocalizationService localize) - { - RuleFor(x => x.DirectDebitAccountHolder).NotEmpty() - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitAccountHolderRequired")); - - - RuleFor(x => x.DirectDebitAccountNumber).NotEmpty().When(x => x.EnterIBAN == "no-iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitAccountNumberRequired")); - - RuleFor(x => x.DirectDebitBankCode).NotEmpty().When(x => x.EnterIBAN == "no-iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitBankCodeRequired")); - - - RuleFor(x => x.DirectDebitIban).Matches(RegularExpressions.IsIban).When(x => x.EnterIBAN == "iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitIbanRequired")); - - RuleFor(x => x.DirectDebitBic).Matches(RegularExpressions.IsBic).When(x => x.EnterIBAN == "iban") - .WithMessage(localize.GetResource("Plugins.Payments.DirectDebit.DirectDebitBicRequired")); - }} -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs b/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs index ef4d0e125b..a7fac39eb0 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs +++ b/src/Plugins/SmartStore.OfflinePayment/Validators/ManualPaymentInfoValidator.cs @@ -14,8 +14,8 @@ public ManualPaymentInfoValidator(ILocalizationService localizationService) //http://benjii.me/2010/11/credit-card-validator-attribute-for-asp-net-mvc-3/ RuleFor(x => x.CardholderName).NotEmpty().WithMessage(localizationService.GetResource("Payment.CardholderName.Required")); - RuleFor(x => x.CardNumber).IsCreditCard().WithMessage(localizationService.GetResource("Payment.CardNumber.Wrong")); - RuleFor(x => x.CardCode).Matches(@"^[0-9]{3,4}$").WithMessage(localizationService.GetResource("Payment.CardCode.Wrong")); + RuleFor(x => x.CardNumber).CreditCard().WithMessage(localizationService.GetResource("Payment.CardNumber.Wrong")); + RuleFor(x => x.CardCode).CreditCardCvvNumber(); } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml index 695f25cf62..6893c04136 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/DirectDebitPaymentInfo.cshtml @@ -4,28 +4,38 @@ Layout = ""; } -

    - @Html.Raw(Model.DescriptionText) -

    - @Html.Hidden("OfflinePaymentMethodType", "DirectDebit") +@if (Model.ThumbnailUrl.HasValue()) +{ +
    + +
    + @Html.Raw(Model.DescriptionText) +
    +
    +} +else +{ +
    + @Html.Raw(Model.DescriptionText) +
    +} +
    @Html.LabelFor(model => model.EnterIBAN, new { @class = "col-md-3 col-form-label" })
    -
    - -
    -
    - -
    +
    +
    + + +
    +
    + + +
    +
    diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericConfigure.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericConfigure.cshtml index 037b2f5081..742bd29fb7 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericConfigure.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericConfigure.cshtml @@ -1,6 +1,6 @@ @model SmartStore.OfflinePayment.Models.ConfigurationModelBase @using SmartStore.Web.Framework; - +@using SmartStore.Web.Framework.UI; @{ Layout = ""; } @@ -22,17 +22,25 @@ @Html.SmartLabelFor(model => model.DescriptionText) - @Html.SettingOverrideCheckbox(model => model.DescriptionText) - @Html.TextAreaFor(model => model.DescriptionText, new { style = "height: 100px;" }) + @Html.SettingEditorFor(model => model.DescriptionText, Html.TextAreaFor(model => model.DescriptionText, new { style = "height: 100px;" })) @Html.ValidationMessageFor(model => model.DescriptionText) + + + @Html.SmartLabelFor(model => model.PaymentMethodLogo) + + + @Html.SettingEditorFor(model => model.PaymentMethodLogo, Html.EditorFor(m => m.PaymentMethodLogo, "Picture", new { transientUpload = true, validate = true })) + @Html.ValidationMessageFor(model => model.PaymentMethodLogo) + + @Html.SmartLabelFor(model => model.AdditionalFee) - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee) @@ -45,6 +53,5 @@ @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) - } \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericPaymentInfo.cshtml index ceb5ed67e3..427c71560c 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericPaymentInfo.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/GenericPaymentInfo.cshtml @@ -4,6 +4,18 @@ Layout = ""; } -
    - @Html.Raw(Model.DescriptionText) -
    \ No newline at end of file +@if (Model.ThumbnailUrl.HasValue()) +{ +
    + +
    + @Html.Raw(Model.DescriptionText) +
    +
    +} +else +{ +
    + @Html.Raw(Model.DescriptionText) +
    +} diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml index b92c72de89..d30cf66665 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualConfigure.cshtml @@ -16,13 +16,30 @@
    + + + + + + + + @@ -30,8 +47,8 @@ @Html.SmartLabelFor(model => model.ExcludedCreditCards) @@ -40,7 +57,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.cshtml index df6ae6e681..0a4fab6d28 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/ManualPaymentInfo.cshtml @@ -6,6 +6,23 @@ @Html.Hidden("OfflinePaymentMethodType", "Manual") +@if (Model.ThumbnailUrl.HasValue()) +{ +
    + +
    + @Html.Raw(Model.DescriptionText) +
    +
    +} +else +{ +
    + @Html.Raw(Model.DescriptionText) +
    +} + +
    @Html.LabelFor(model => model.CreditCardTypes, new { @class = "col-md-3 col-form-label required" }) diff --git a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml index 07345c2ae0..44a83f82e6 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml +++ b/src/Plugins/SmartStore.OfflinePayment/Views/OfflinePayment/PurchaseOrderNumberPaymentInfo.cshtml @@ -7,6 +7,22 @@ @Html.Hidden("OfflinePaymentMethodType", "PurchaseOrderNumber") +@if (Model.ThumbnailUrl.HasValue()) +{ +
    + +
    + @Html.Raw(Model.DescriptionText) +
    +
    +} +else +{ +
    + @Html.Raw(Model.DescriptionText) +
    +} +
    @Html.ControlGroupFor(model => model.PurchaseOrderNumber, required: true, breakpoint: "md")
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/packages.config b/src/Plugins/SmartStore.OfflinePayment/packages.config index 202103c1d8..ee4c4aa5f3 100644 --- a/src/Plugins/SmartStore.OfflinePayment/packages.config +++ b/src/Plugins/SmartStore.OfflinePayment/packages.config @@ -1,7 +1,7 @@  - + diff --git a/src/Plugins/SmartStore.PayPal/Content/branding.png b/src/Plugins/SmartStore.PayPal/Content/branding.png index bcd99dc6b2..9fba00608a 100644 Binary files a/src/Plugins/SmartStore.PayPal/Content/branding.png and b/src/Plugins/SmartStore.PayPal/Content/branding.png differ diff --git a/src/Plugins/SmartStore.PayPal/Content/icon.png b/src/Plugins/SmartStore.PayPal/Content/icon.png index 1a2423cef2..04beef7c77 100644 Binary files a/src/Plugins/SmartStore.PayPal/Content/icon.png and b/src/Plugins/SmartStore.PayPal/Content/icon.png differ diff --git a/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css b/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css index e567cd3448..8c2faa5c54 100644 --- a/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css +++ b/src/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css @@ -4,13 +4,6 @@ padding: 12px 15px 5px 0; } -#paypal-checkout .form-horizontal { - text-align: right; -} - -#paypal-checkout #paypal-express-button { - cursor: pointer; -} .paypal-standard-public { margin-bottom: 10px; } diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs index 8db1218d7d..60320c45d9 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Web; using System.Web.Mvc; @@ -10,6 +11,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; +using SmartStore.PayPal.Models; using SmartStore.PayPal.Settings; using SmartStore.Services.Orders; using SmartStore.Services.Payments; @@ -17,7 +19,19 @@ namespace SmartStore.PayPal.Controllers { - public abstract class PayPalControllerBase : PaymentControllerBase where TSetting : PayPalSettingsBase, ISettings, new() + public abstract class PayPalPaymentControllerBase : PaymentControllerBase + { + protected void PrepareConfigurationModel(ApiConfigurationModel model, int storeScope) + { + var store = storeScope == 0 + ? Services.StoreContext.CurrentStore + : Services.StoreService.GetStoreById(storeScope); + + model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; + } + } + + public abstract class PayPalControllerBase : PayPalPaymentControllerBase where TSetting : PayPalSettingsBase, ISettings, new() { public PayPalControllerBase( string systemName, @@ -87,10 +101,9 @@ protected PaymentStatus GetPaymentStatus(string paymentStatus, string pendingRea protected bool VerifyIPN(PayPalSettingsBase settings, string formString, out Dictionary values) { - // settings: multistore context not possible here. we need the custom value to determine what store it is. - - var request = settings.GetPayPalWebRequest(); - request.Method = "POST"; + // Settings: multistore context not possible here. we need the custom value to determine what store it is. + var request = (HttpWebRequest)WebRequest.Create(settings.GetPayPalUrl()); + request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; request.UserAgent = Request.UserAgent; diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs index 3051f4c980..3932a4a301 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs @@ -1,22 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Payments; using SmartStore.PayPal.Models; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.PayPal.Validators; using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; namespace SmartStore.PayPal.Controllers { - public class PayPalDirectController : PayPalControllerBase + public class PayPalDirectController : PayPalControllerBase { private readonly HttpContextBase _httpContext; @@ -34,53 +32,53 @@ public PayPalDirectController( _httpContext = httpContext; } - private SelectList TransactModeValues(TransactMode selected) - { - return new SelectList(new List - { - new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalDirect.ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalDirect.ModeAuthAndCapture") } - }, - "ID", "Name", (int)selected); - } - [LoadSetting, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalDirectPaymentSettings settings) + public ActionResult Configure(PayPalDirectPaymentSettings settings, int storeScope) { var model = new PayPalDirectConfigurationModel(); model.Copy(settings, true); - model.TransactModeValues = TransactModeValues(settings.TransactMode); - model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() - .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) - .ToList(); + PrepareConfigurationModel(model, storeScope); return View(model); } - [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalDirectPaymentSettings settings, PayPalDirectConfigurationModel model) + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(PayPalDirectConfigurationModel model, FormCollection form) { - if (!ModelState.IsValid) - return Configure(settings); + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + if (!ModelState.IsValid) + { + return Configure(settings, storeScope); + } ModelState.Clear(); + model.Copy(settings, false); - model.Copy(settings, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - // multistore context not possible, see IPN handling - Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + using (Services.Settings.BeginScope()) + { + // Multistore context not possible, see IPN handling. + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + } - NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); - return Configure(settings); + return RedirectToConfiguration(PayPalDirectProvider.SystemName, false); } public ActionResult PaymentInfo() { var model = new PayPalDirectPaymentInfoModel(); - //CC types + // Credit card types. model.CreditCardTypes.Add(new SelectListItem { Text = "Visa", @@ -102,7 +100,7 @@ public ActionResult PaymentInfo() Value = "Amex", }); - //years + // Years. for (int i = 0; i < 15; i++) { string year = Convert.ToString(DateTime.Now.Year + i); @@ -113,7 +111,7 @@ public ActionResult PaymentInfo() }); } - //months + // Months. for (int i = 1; i <= 12; i++) { string text = (i < 10) ? "0" + i.ToString() : i.ToString(); @@ -124,7 +122,7 @@ public ActionResult PaymentInfo() }); } - //set postback values + // Set postback values. var paymentData = _httpContext.GetCheckoutState().PaymentData; model.CardholderName = (string)paymentData.Get("CardholderName"); model.CardNumber = (string)paymentData.Get("CardNumber"); @@ -152,7 +150,7 @@ public ActionResult PaymentInfo() public override IList ValidatePaymentForm(FormCollection form) { var warnings = new List(); - var validator = new PaymentInfoValidator(Services.Localization); + var validator = new PaymentInfoValidator(T); var model = new PayPalDirectPaymentInfoModel { diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index b29d5f5cf6..faed628e5e 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -1,20 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Web.Mvc; -using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Logging; using SmartStore.PayPal.Models; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.PayPal.Validators; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; @@ -23,10 +14,14 @@ using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.Mvc; namespace SmartStore.PayPal.Controllers { - public class PayPalExpressController : PayPalControllerBase + public class PayPalExpressController : PayPalControllerBase { private readonly OrderSettings _orderSettings; private readonly ICurrencyService _currencyService; @@ -55,16 +50,6 @@ public PayPalExpressController( _genericAttributeService = genericAttributeService; } - private SelectList TransactModeValues(TransactMode selected) - { - return new SelectList(new List - { - new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalExpress.ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalExpress.ModeAuthAndCapture") } - }, - "ID", "Name", (int)selected); - } - private string GetCheckoutButtonUrl(PayPalExpressPaymentSettings settings) { var expressCheckoutButton = "~/Plugins/SmartStore.PayPal/Content/checkout-button-default.png"; @@ -83,38 +68,46 @@ private string GetCheckoutButtonUrl(PayPalExpressPaymentSettings settings) } - [LoadSetting, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalExpressPaymentSettings settings) + [AdminAuthorize, ChildActionOnly, LoadSetting] + public ActionResult Configure(PayPalExpressPaymentSettings settings, int storeScope) { var model = new PayPalExpressConfigurationModel(); - model.Copy(settings, true); - model.TransactModeValues = TransactModeValues(settings.TransactMode); - - model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() - .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) - .ToList(); + PrepareConfigurationModel(model, storeScope); return View(model); } - [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalExpressPaymentSettings settings, PayPalExpressConfigurationModel model) + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(PayPalExpressConfigurationModel model, FormCollection form) { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + if (!ModelState.IsValid) - return Configure(settings); + { + return Configure(settings, storeScope); + } ModelState.Clear(); + model.Copy(settings, false); - model.Copy(settings, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - // multistore context not possible, see IPN handling - Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + using (Services.Settings.BeginScope()) + { + // Multistore context not possible, see IPN handling. + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + } - NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); - return Configure(settings); + return RedirectToConfiguration(PayPalExpressProvider.SystemName, false); } public ActionResult PaymentInfo() @@ -329,20 +322,8 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) public override IList ValidatePaymentForm(FormCollection form) { var warnings = new List(); - - var validator = new PayPalExpressPaymentInfoValidator(Services.Localization); var model = new PayPalExpressPaymentInfoModel(); - - var validationResult = validator.Validate(model); - - if (!validationResult.IsValid) - { - foreach (var error in validationResult.Errors) - { - warnings.Add(error.ErrorMessage); - } - } - + return warnings; } } diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 882328fa3c..d3433312a2 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Mvc; +using Newtonsoft.Json; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Stores; @@ -12,7 +7,6 @@ using SmartStore.PayPal.Models; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.PayPal.Validators; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; @@ -20,14 +14,22 @@ using SmartStore.Services.Localization; using SmartStore.Services.Payments; using SmartStore.Services.Tax; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; +using SmartStore.Web.Framework.Theming; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Mvc; namespace SmartStore.PayPal.Controllers { - public class PayPalPlusController : PayPalRestApiControllerBase + public class PayPalPlusController : PayPalRestApiControllerBase { private readonly HttpContextBase _httpContext; private readonly PluginMediator _pluginMediator; @@ -105,7 +107,7 @@ private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMeth var paymentMethod = _paymentService.GetPaymentMethodBySystemName(provider.Metadata.SystemName); if (paymentMethod != null) { - var description = paymentMethod.GetLocalized(x => x.FullDescription); + string description = paymentMethod.GetLocalized(x => x.FullDescription); if (description.HasValue()) { description = HtmlUtils.ConvertHtmlToPlainText(description); @@ -153,7 +155,7 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) return paymentInfo; } - [LoadSetting, AdminAuthorize, ChildActionOnly] + [LoadSetting, AdminAuthorize, ChildActionOnly, AdminThemed] public ActionResult Configure(PayPalPlusPaymentSettings settings, int storeScope) { var model = new PayPalPlusConfigurationModel @@ -161,36 +163,34 @@ public ActionResult Configure(PayPalPlusPaymentSettings settings, int storeScope ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") }; - model.AvailableSecurityProtocols = PayPal.Services.PayPalService.GetSecurityProtocols() - .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) - .ToList(); + // It's better to also offer inactive methods here but filter them out in frontend. + var paymentMethods = _paymentService.LoadAllPaymentMethods(storeScope); - // it's better to also offer inactive methods here but filter them out in frontend - var methods = _paymentService.LoadAllPaymentMethods(storeScope); + model.Copy(settings, true); + PrepareConfigurationModel(model, storeScope); - model.AvailableThirdPartyPaymentMethods = methods - .Where(x => + model.AvailableThirdPartyPaymentMethods = paymentMethods + .Where(x => x.Metadata.PluginDescriptor.SystemName != Plugin.SystemName && !x.Value.RequiresInteraction && (x.Metadata.PluginDescriptor.SystemName == "SmartStore.OfflinePayment" || x.Value.PaymentMethodType == PaymentMethodType.Redirection)) - .Select(x => new SelectListItem { Value = x.Metadata.SystemName, Text = GetPaymentMethodName(x) }) - .ToList(); - - - model.Copy(settings, true); + .ToSelectListItems(_pluginMediator, model.ThirdPartyPaymentMethods.ToArray()); return View(model); } - [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalPlusPaymentSettings settings, PayPalPlusConfigurationModel model, FormCollection form, int storeScope) + [HttpPost, AdminAuthorize, ChildActionOnly, AdminThemed] + public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection form) { var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + var oldClientId = settings.ClientId; var oldSecret = settings.Secret; var oldProfileId = settings.ExperienceProfileId; - var validator = new PayPalPlusConfigValidator(Services.Localization, x => + var validator = new PayPalPlusConfigValidator(T, x => { return storeScope == 0 || storeDependingSettingHelper.IsOverrideChecked(settings, x, form); }); @@ -198,13 +198,14 @@ public ActionResult Configure(PayPalPlusPaymentSettings settings, PayPalPlusConf validator.Validate(model, ModelState); if (!ModelState.IsValid) + { return Configure(settings, storeScope); + } ModelState.Clear(); - model.Copy(settings, false); - // credentials changed: reset profile and webhook id to avoid errors + // Credentials changed: reset profile and webhook id to avoid errors. if (!oldClientId.IsCaseInsensitiveEqual(settings.ClientId) || !oldSecret.IsCaseInsensitiveEqual(settings.Secret)) { if (oldProfileId.IsCaseInsensitiveEqual(settings.ExperienceProfileId)) @@ -213,11 +214,20 @@ public ActionResult Configure(PayPalPlusPaymentSettings settings, PayPalPlusConf settings.WebhookId = null; } - Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } + + using (Services.Settings.BeginScope()) + { + // Multistore context not possible, see IPN handling. + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + } NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); - return Configure(settings, storeScope); + return RedirectToConfiguration(PayPalPlusProvider.SystemName, false); } public ActionResult PaymentInfo() @@ -238,7 +248,8 @@ public ActionResult PaymentWall() var pppProvider = _paymentService.LoadPaymentMethodBySystemName(PayPalPlusProvider.SystemName, false, store.Id); var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); - var session = _httpContext.GetPayPalSessionData(); + var session = _httpContext.GetPayPalState(PayPalPlusProvider.SystemName); + var redirectToConfirm = false; var model = new PayPalPlusCheckoutModel(); model.ThirdPartyPaymentMethods = new List(); @@ -250,7 +261,7 @@ public ActionResult PaymentWall() if (pppMethod != null) { - model.FullDescription = pppMethod.GetLocalized(x => x.FullDescription, language.Id); + model.FullDescription = pppMethod.GetLocalized(x => x.FullDescription, language); } if (customer.BillingAddress != null && customer.BillingAddress.Country != null) @@ -277,7 +288,7 @@ public ActionResult PaymentWall() model.ThirdPartyFees = sb.ToString(); - // we must create a new paypal payment each time the payment wall is rendered because otherwise patch payment can fail + // We must create a new paypal payment each time the payment wall is rendered because otherwise patch payment can fail // with "Item amount must add up to specified amount subtotal (or total if amount details not specified)". session.PaymentId = null; session.ApprovalUrl = null; @@ -292,10 +303,10 @@ public ActionResult PaymentWall() result = PayPalService.CreatePayment(settings, session, cart, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); if (result == null) { - return RedirectToAction("Confirm", "Checkout", new { area = "" }); + // No payment required. + redirectToConfirm = true; } - - if (result.Success && result.Json != null) + else if (result.Success && result.Json != null) { foreach (var link in result.Json.links) { @@ -319,7 +330,18 @@ public ActionResult PaymentWall() model.ApprovalUrl = session.ApprovalUrl; - if (session.SessionExpired) + // There have been cases where the token was lost for unexplained reasons, so it is additionally stored in the database. + var sessionData = session.AccessToken.HasValue() && session.PaymentId.HasValue() + ? JsonConvert.SerializeObject(session) + : null; + _genericAttributeService.SaveAttribute(customer, PayPalPlusProvider.SystemName + ".SessionData", sessionData, store.Id); + + if (redirectToConfirm) + { + return RedirectToAction("Confirm", "Checkout", new { area = "" }); + } + + if (session.SessionExpired) { // Customer has been redirected because the session expired. session.SessionExpired = false; @@ -332,7 +354,10 @@ public ActionResult PaymentWall() [HttpPost] public ActionResult PatchShipping() { - var session = HttpContext.GetPayPalSessionData(); + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var session = _httpContext.GetPayPalState(PayPalPlusProvider.SystemName, customer, store.Id, _genericAttributeService); + if (session.AccessToken.IsEmpty() || session.PaymentId.IsEmpty()) { // Session expired. Reload payment wall and create new payment (we need the payment id). @@ -341,8 +366,6 @@ public ActionResult PatchShipping() return new JsonResult { Data = new { success = false, error = string.Empty, reload = true } }; } - var store = Services.StoreContext.CurrentStore; - var customer = Services.WorkContext.CurrentCustomer; var settings = Services.Settings.LoadSetting(store.Id); var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); @@ -359,9 +382,13 @@ public ActionResult PatchShipping() public ActionResult CheckoutCompleted() { - var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; - if (instruct.HasValue()) + _genericAttributeService.SaveAttribute(customer, PayPalPlusProvider.SystemName + ".SessionData", (string)null, store.Id); + + var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + if (instruct.HasValue()) { return Content(instruct); } @@ -372,13 +399,13 @@ public ActionResult CheckoutCompleted() [ValidateInput(false)] public ActionResult CheckoutReturn(string systemName, string paymentId, string PayerID) { - // Request.QueryString: - // paymentId: PAY-0TC88803RP094490KK4KM6AI, token (not the access token): EC-5P379249AL999154U, PayerID: 5L9K773HHJLPN + // Request.QueryString: + // paymentId: PAY-0TC88803RP094490KK4KM6AI, token (not the access token): EC-5P379249AL999154U, PayerID: 5L9K773HHJLPN - var customer = Services.WorkContext.CurrentCustomer; - var store = Services.StoreContext.CurrentStore; + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; var settings = Services.Settings.LoadSetting(store.Id); - var session = _httpContext.GetPayPalSessionData(); + var session = _httpContext.GetPayPalState(PayPalPlusProvider.SystemName); if (systemName.IsEmpty()) systemName = PayPalPlusProvider.SystemName; diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs index d80693337b..c28bb24fcd 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs @@ -11,7 +11,7 @@ namespace SmartStore.PayPal.Controllers { - public abstract class PayPalRestApiControllerBase : PaymentControllerBase where TSetting : PayPalApiSettingsBase, ISettings, new() + public abstract class PayPalRestApiControllerBase : PayPalPaymentControllerBase where TSetting : PayPalApiSettingsBase, ISettings, new() { public PayPalRestApiControllerBase( string systemName, diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs index cf3d300aed..0d4d6a7605 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs @@ -3,11 +3,9 @@ using System.Globalization; using System.Linq; using System.Web.Mvc; -using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; using SmartStore.PayPal.Models; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Orders; using SmartStore.Services.Payments; @@ -30,34 +28,46 @@ public PayPalStandardController( { } - [LoadSetting, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalStandardPaymentSettings settings) + [AdminAuthorize, ChildActionOnly, LoadSetting] + public ActionResult Configure(PayPalStandardPaymentSettings settings, int storeScope) { var model = new PayPalStandardConfigurationModel(); model.Copy(settings, true); - model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() - .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) - .ToList(); + PrepareConfigurationModel(model, storeScope); return View(model); } - [SaveSetting, HttpPost, AdminAuthorize, ChildActionOnly] - public ActionResult Configure(PayPalStandardPaymentSettings settings, PayPalStandardConfigurationModel model, FormCollection form) + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(PayPalStandardConfigurationModel model, FormCollection form) { - if (!ModelState.IsValid) - return Configure(settings); + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + if (!ModelState.IsValid) + { + return Configure(settings, storeScope); + } ModelState.Clear(); - model.Copy(settings, false); + model.Copy(settings, false); + + using (Services.Settings.BeginScope()) + { + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + } - // multistore context not possible, see IPN handling - Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + using (Services.Settings.BeginScope()) + { + // Multistore context not possible, see IPN handling. + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + } - NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); - return Configure(settings); + return RedirectToConfiguration(PayPalStandardProvider.SystemName, false); } public ActionResult PaymentInfo() diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 9bb5fc6f74..b23fa90c94 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,8 +2,8 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 3.0.3.1 -MinAppVersion: 3.0.0 +Version: 3.1.5.2 +MinAppVersion: 3.1.5 DisplayOrder: 1 FileName: SmartStore.PayPal.dll ResourceRootKey: Plugins.SmartStore.PayPal diff --git a/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs index 1bc07950a4..13e6e80e1a 100644 --- a/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs @@ -1,12 +1,13 @@ -using System.Net; -using System.Web; +using System.Web; +using Newtonsoft.Json; +using SmartStore.Core.Domain.Customers; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; -using SmartStore.Services.Orders; +using SmartStore.Services.Common; namespace SmartStore.PayPal { - internal static class MiscExtensions + internal static class MiscExtensions { public static string GetPayPalUrl(this PayPalSettingsBase settings) { @@ -15,26 +16,54 @@ public static string GetPayPalUrl(this PayPalSettingsBase settings) "https://www.paypal.com/cgi-bin/webscr"; } - public static HttpWebRequest GetPayPalWebRequest(this PayPalSettingsBase settings) + public static PayPalSessionData GetPayPalState(this HttpContextBase httpContext, string key) { - if (settings.SecurityProtocol.HasValue) - { - ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; - } + Guard.NotEmpty(key, nameof(key)); - var request = (HttpWebRequest)WebRequest.Create(GetPayPalUrl(settings)); - return request; + var state = httpContext.GetCheckoutState(); + + if (!state.CustomProperties.ContainsKey(key)) + { + state.CustomProperties.Add(key, new PayPalSessionData()); + } + + var session = state.CustomProperties.Get(key) as PayPalSessionData; + return session; } - public static PayPalSessionData GetPayPalSessionData(this HttpContextBase httpContext, CheckoutState state = null) - { - if (state == null) - state = httpContext.GetCheckoutState(); + public static PayPalSessionData GetPayPalState( + this HttpContextBase httpContext, + string key, + Customer customer, + int storeId, + IGenericAttributeService genericAttributeService) + { + Guard.NotNull(httpContext, nameof(httpContext)); + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(genericAttributeService, nameof(genericAttributeService)); - if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) - state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); + var session = httpContext.GetPayPalState(key); - return state.CustomProperties.Get(PayPalPlusProvider.SystemName) as PayPalSessionData; - } - } + if (session.AccessToken.IsEmpty() || session.PaymentId.IsEmpty()) + { + try + { + var str = customer.GetAttribute(key + ".SessionData", genericAttributeService, storeId); + if (str.HasValue()) + { + var storedSessionData = JsonConvert.DeserializeObject(str); + if (storedSessionData != null) + { + // Only token and paymentId required. + session.AccessToken = storedSessionData.AccessToken; + session.PaymentId = storedSessionData.PaymentId; + } + } + } + catch { } + } + + return session; + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs index 935f71b337..29a9f38bb3 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs @@ -1,20 +1,15 @@ using System; -using System.Web; using System.Web.Mvc; using SmartStore.Web.Framework.UI; namespace SmartStore.PayPal.Filters { - public class PayPalPlusWidgetZoneFilter : IActionFilter, IResultFilter + public class PayPalPlusWidgetZoneFilter : IActionFilter, IResultFilter { - private readonly Lazy _httpContext; private readonly Lazy _widgetProvider; - public PayPalPlusWidgetZoneFilter( - Lazy httpContext, - Lazy widgetProvider) + public PayPalPlusWidgetZoneFilter(Lazy widgetProvider) { - _httpContext = httpContext; _widgetProvider = widgetProvider; } @@ -31,7 +26,7 @@ public void OnResultExecuting(ResultExecutingContext filterContext) if (filterContext.IsChildAction) return; - // should only run on a full view rendering result + // Should only run on a full view rendering result. var result = filterContext.Result as ViewResultBase; if (result == null) return; @@ -41,12 +36,7 @@ public void OnResultExecuting(ResultExecutingContext filterContext) if (action.IsCaseInsensitiveEqual("Completed") && controller.IsCaseInsensitiveEqual("Checkout")) { - var instruct = _httpContext.Value.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; - - if (instruct.HasValue()) - { - _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); - } + _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); } } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 8c6247de59..18140115e2 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -95,12 +95,6 @@ Bestimmen Sie den Transaktionsmodus. - - Sicherheitsprotokoll - - - Legt das mit der PayPal-API zu verwendende Sicherheitsprotokoll fest. - API Benutzername @@ -119,18 +113,6 @@ Geben Sie Ihre Signatur ein. - - Zusätzliche Gebühren - - - Zusätzliche Gebühren, die dem Kunden berechnet werden sollen. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. - PayPal Adresse anzeigen @@ -190,7 +172,7 @@
    1. In Ihr Premier- oder Business-Konto einloggen.
    2. Auf den Register Mein Profil klicken.
    3. -
    4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    5. +
    6. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    7. Sofortige Zahlungsbestätigung klicken.
    8. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
    9. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler) eingeben.
    10. @@ -264,7 +246,7 @@
      1. In Ihr Premier- oder Business-Konto einloggen.
      2. Auf den Register Mein Profil klicken.
      3. -
      4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
      5. +
      6. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
      7. Sofortige Zahlungsbestätigung klicken.
      8. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
      9. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler) eingeben.
      10. @@ -317,7 +299,7 @@
        1. In Ihr Premier- oder Business-Konto einloggen.
        2. Auf den Register Mein Profil klicken.
        3. -
        4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
        5. +
        6. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
        7. Zurück zu Mein Profil und auf Sofortige Zahlungsbestätigung klicken.
        8. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
        9. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler) eingeben.
        10. diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 70f7133bab..d0a439de43 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -98,12 +98,6 @@ Specify the payment transaction mode. - - Security protocol - - - Specifies the security protocol to use with the PayPal API. - API Account Name @@ -122,18 +116,6 @@ Enter the signature. - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage. - - - Specifies whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. - Show PayPal address diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index aa7363ae72..862b281c6b 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -1,19 +1,20 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Web.Mvc; using SmartStore.ComponentModel; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using SmartStore.Web.Framework.Validators; +using SmartStore.Core.Localization; +using System; +using FluentValidation; namespace SmartStore.PayPal.Models { - public abstract class ApiConfigurationModel: ModelBase + public abstract class ApiConfigurationModel : ModelBase { public string[] ConfigGroups { get; set; } + public string PrimaryStoreCurrencyCode { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] public bool UseSandbox { get; set; } @@ -22,12 +23,7 @@ public abstract class ApiConfigurationModel: ModelBase public bool IpnChangesPaymentStatus { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.TransactMode")] - public int TransactMode { get; set; } - public SelectList TransactModeValues { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.PayPal.SecurityProtocol")] - public SecurityProtocolType? SecurityProtocol { get; set; } - public List AvailableSecurityProtocols { get; set; } + public TransactMode TransactMode { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.ApiAccountName")] public string ApiAccountName { get; set; } @@ -51,10 +47,10 @@ public abstract class ApiConfigurationModel: ModelBase [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookId")] public string WebhookId { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFeePercentage")] + [SmartResourceDisplayName("Admin.Configuration.Payment.Methods.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } } @@ -63,9 +59,20 @@ public class PayPalDirectConfigurationModel : ApiConfigurationModel public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) { if (fromSettings) + { MiniMapper.Map(settings, this); - else + } + else + { MiniMapper.Map(this, settings); + settings.ApiAccountName = ApiAccountName.TrimSafe(); + settings.ApiAccountPassword = ApiAccountPassword.TrimSafe(); + settings.ClientId = ClientId.TrimSafe(); + settings.ExperienceProfileId = ExperienceProfileId.TrimSafe(); + settings.Secret = Secret.TrimSafe(); + settings.Signature = Signature.TrimSafe(); + settings.WebhookId = WebhookId.TrimSafe(); + } } } @@ -89,18 +96,29 @@ public class PayPalExpressConfigurationModel : ApiConfigurationModel public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) { if (fromSettings) + { MiniMapper.Map(settings, this); + } else + { MiniMapper.Map(this, settings); + settings.ApiAccountName = ApiAccountName.TrimSafe(); + settings.ApiAccountPassword = ApiAccountPassword.TrimSafe(); + settings.Signature = Signature.TrimSafe(); + } } } - - + public class PayPalPlusConfigurationModel : ApiConfigurationModel { + public PayPalPlusConfigurationModel() + { + TransactMode = TransactMode.AuthorizeAndCapture; + } + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.ThirdPartyPaymentMethods")] public List ThirdPartyPaymentMethods { get; set; } - public List AvailableThirdPartyPaymentMethods { get; set; } + public IList AvailableThirdPartyPaymentMethods { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.DisplayPaymentMethodLogo")] public bool DisplayPaymentMethodLogo { get; set; } @@ -114,13 +132,41 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) if (fromSettings) { MiniMapper.Map(settings, this); - TransactMode = (int)Settings.TransactMode.AuthorizeAndCapture; } else { MiniMapper.Map(this, settings); - settings.TransactMode = Settings.TransactMode.AuthorizeAndCapture; + settings.ApiAccountName = ApiAccountName.TrimSafe(); + settings.ApiAccountPassword = ApiAccountPassword.TrimSafe(); + settings.ClientId = ClientId.TrimSafe(); + settings.ExperienceProfileId = ExperienceProfileId.TrimSafe(); + settings.Secret = Secret.TrimSafe(); + settings.Signature = Signature.TrimSafe(); + settings.WebhookId = WebhookId.TrimSafe(); } } } + + public class PayPalPlusConfigValidator : SmartValidatorBase + { + public PayPalPlusConfigValidator(Localizer T, Func addRule) + { + if (addRule("ClientId")) + { + RuleFor(x => x.ClientId).NotEmpty(); + } + + if (addRule("Secret")) + { + RuleFor(x => x.Secret).NotEmpty(); + } + + if (addRule("ThirdPartyPaymentMethods")) + { + RuleFor(x => x.ThirdPartyPaymentMethods) + .Must(x => x == null || x.Count <= 5) + .WithMessage(T("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); + } + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs index 7f2d0cfbd3..7790f13fc9 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalDirectPaymentInfoModel.cs @@ -2,6 +2,9 @@ using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; +using FluentValidation; +using SmartStore.Core.Localization; +using SmartStore.Web.Framework.Validators; namespace SmartStore.PayPal.Models { @@ -44,4 +47,16 @@ public PayPalDirectPaymentInfoModel() [AllowHtml] public string CardCode { get; set; } } + + public class PaymentInfoValidator : AbstractValidator + { + public PaymentInfoValidator(Localizer T) + { + RuleFor(x => x.CardholderName).NotEmpty(); + RuleFor(x => x.ExpireMonth).NotEmpty(); + RuleFor(x => x.ExpireYear).NotEmpty(); + RuleFor(x => x.CardNumber).CreditCard().WithMessage(T("Payment.CardNumber.Wrong")); + RuleFor(x => x.CardCode).CreditCardCvvNumber(); + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs index b9e71a9e71..c0a8ab99f6 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs @@ -1,25 +1,11 @@ -using System.Collections.Generic; -using System.Net; -using System.Web.Mvc; -using SmartStore.ComponentModel; +using SmartStore.ComponentModel; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { - public class PayPalStandardConfigurationModel : ModelBase + public class PayPalStandardConfigurationModel : ApiConfigurationModel { - [SmartResourceDisplayName("Plugins.Payments.PayPal.SecurityProtocol")] - public SecurityProtocolType? SecurityProtocol { get; set; } - public List AvailableSecurityProtocols { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] - public bool UseSandbox { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.PayPal.IpnChangesPaymentStatus")] - public bool IpnChangesPaymentStatus { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.BusinessEmail")] public string BusinessEmail { get; set; } @@ -32,12 +18,6 @@ public class PayPalStandardConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PdtValidateOnlyWarn")] public bool PdtValidateOnlyWarn { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] - public decimal AdditionalFee { get; set; } - - [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFeePercentage")] - public bool AdditionalFeePercentage { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.IsShippingAddressRequired")] public bool IsShippingAddressRequired { get; set; } @@ -56,9 +36,14 @@ public class PayPalStandardConfigurationModel : ModelBase public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) { if (fromSettings) + { MiniMapper.Map(settings, this); + } else + { MiniMapper.Map(this, settings); + settings.BusinessEmail = BusinessEmail.TrimSafe(); + } } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index 45e251d2d0..95ab5d5cbd 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -11,18 +11,14 @@ namespace SmartStore.PayPal [DisplayOrder(1)] public partial class PayPalPlusProvider : PayPalRestApiProviderBase { - public static string SystemName - { - get { return "Payments.PayPalPlus"; } - } + public PayPalPlusProvider() + : base(SystemName) + { + } - public override PaymentMethodType PaymentMethodType - { - get - { - return PaymentMethodType.StandardAndRedirection; - } - } + public static string SystemName => "Payments.PayPalPlus"; + + public override PaymentMethodType PaymentMethodType => PaymentMethodType.StandardAndRedirection; public override Type GetControllerType() { diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs index 1c65bd39dd..10cf99276b 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs @@ -59,11 +59,6 @@ public override bool SupportVoid protected PayPalAPIAASoapBinding GetApiAaService(TSetting settings) { - if (settings.SecurityProtocol.HasValue) - { - ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; - } - var service = new PayPalAPIAASoapBinding(); service.Url = settings.UseSandbox ? "https://api-3t.sandbox.paypal.com/2.0/" : "https://api-3t.paypal.com/2.0/"; @@ -75,11 +70,6 @@ protected PayPalAPIAASoapBinding GetApiAaService(TSetting settings) protected PayPalAPISoapBinding GetApiService(TSetting settings) { - if (settings.SecurityProtocol.HasValue) - { - ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; - } - var service = new PayPalAPISoapBinding(); service.Url = settings.UseSandbox ? "https://api-3t.sandbox.paypal.com/2.0/" : "https://api-3t.paypal.com/2.0/"; diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index 80712abf29..7b83e6d349 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -11,15 +11,22 @@ using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; +using SmartStore.Services.Common; using SmartStore.Services.Orders; using SmartStore.Services.Payments; namespace SmartStore.PayPal { - public abstract class PayPalRestApiProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() + public abstract class PayPalRestApiProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() { - protected PayPalRestApiProviderBase() + private readonly string _providerSystemName; + + protected PayPalRestApiProviderBase(string providerSystemName) { + Guard.NotEmpty(providerSystemName, nameof(providerSystemName)); + + _providerSystemName = providerSystemName; + Logger = NullLogger.Instance; } @@ -28,14 +35,15 @@ protected PayPalRestApiProviderBase() public ICommonServices Services { get; set; } public IOrderService OrderService { get; set; } public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } - public IPayPalService PayPalService { get; set; } + public IGenericAttributeService GenericAttributeService { get; set; } + public IPayPalService PayPalService { get; set; } protected string GetControllerName() { return GetControllerType().Name.EmptyNull().Replace("Controller", ""); } - public static string CheckoutCompletedKey + public static string CheckoutCompletedKey { get { return "PayPalCheckoutCompleted"; } } @@ -75,7 +83,7 @@ public override decimal GetAdditionalHandlingFee(IList(processPaymentRequest.StoreId); - var session = HttpContext.GetPayPalSessionData(); + var storeId = processPaymentRequest.StoreId; + var customer = Services.WorkContext.CurrentCustomer; + var session = HttpContext.GetPayPalState(_providerSystemName, customer, storeId, GenericAttributeService); if (session.AccessToken.IsEmpty() || session.PaymentId.IsEmpty()) { - session.SessionExpired = true; - - // Do not place order because we cannot execute the payment. - result.AddError(T("Plugins.SmartStore.PayPal.SessionExpired")); + // Do not place order because we cannot execute the payment. + session.SessionExpired = true; + result.AddError(T("Plugins.SmartStore.PayPal.SessionExpired")); - // Redirect to payment wall and create new payment (we need the payment id). - var response = HttpContext.Response; - var urlHelper = new UrlHelper(HttpContext.Request.RequestContext); - var isSecure = Services.WebHelper.IsCurrentConnectionSecured(); + // Redirect to payment wall and create new payment (we need the payment id). + var urlHelper = new UrlHelper(HttpContext.Request.RequestContext); + HttpContext.Response.Redirect(urlHelper.Action("PaymentMethod", "Checkout", new { area = "" })); - response.Status = "302 Found"; - response.RedirectLocation = urlHelper.Action("PaymentMethod", "Checkout", new { area = "" }, isSecure ? "https" : "http"); - response.End(); - - return result; + return result; } processPaymentRequest.OrderGuid = session.OrderGuid; - var apiResult = PayPalService.ExecutePayment(settings, session); + var settings = Services.Settings.LoadSetting(storeId); + var apiResult = PayPalService.ExecutePayment(settings, session); if (apiResult.Success && apiResult.Json != null) { @@ -159,7 +163,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces state = (string)relatedObject.state; reasonCode = (string)relatedObject.reason_code; - // see PayPalService.Refund() + // See PayPalService.Refund(). result.AuthorizationTransactionResult = "{0} ({1})".FormatInvariant(state.NaIfEmpty(), intent.NaIfEmpty()); result.AuthorizationTransactionId = (string)relatedObject.id; @@ -190,7 +194,10 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) { - var instruction = PayPalService.CreatePaymentInstruction(HttpContext.GetPayPalSessionData().PaymentInstruction); + var storeId = postProcessPaymentRequest.Order.StoreId; + var customer = Services.WorkContext.CurrentCustomer; + var session = HttpContext.GetPayPalState(_providerSystemName, customer, storeId, GenericAttributeService); + var instruction = PayPalService.CreatePaymentInstruction(session.PaymentInstruction); if (instruction.HasValue()) { @@ -198,7 +205,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay OrderService.AddOrderNote(postProcessPaymentRequest.Order, instruction, true); } - } + } public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) { diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index 56a27c54ef..5ff39c0bfb 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Web; using System.Web.Routing; @@ -331,7 +332,7 @@ public override decimal GetAdditionalHandlingFee(IListResult public bool GetPDTDetails(string tx, PayPalStandardPaymentSettings settings, out Dictionary values, out string response) { - var request = settings.GetPayPalWebRequest(); + var request = (HttpWebRequest)WebRequest.Create(settings.GetPayPalUrl()); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; @@ -426,19 +427,6 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa cartTotal += orderItem.PriceExclTax; } - // Rounding - if (order.OrderTotalRounding != decimal.Zero) - { - var item = new PayPalLineItem - { - Type = PayPalItemType.Rounding, - Name = T("ShoppingCart.Totals.Rounding").Text, - Quantity = 1, - Amount = order.OrderTotalRounding - }; - lst.Add(item); - } - // Shipping if (order.OrderShippingExclTax > decimal.Zero) { @@ -623,7 +611,6 @@ public enum PayPalItemType CartItem = 0, Shipping, PaymentFee, - Tax, - Rounding + Tax } } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 4af221f7c8..0289f48f6f 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -12,7 +12,6 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Stores; @@ -153,7 +152,7 @@ private Dictionary CreateAmount( { var line = new Dictionary(); line.Add("quantity", item.Item.Quantity); - line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); + line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language, true, false).Value.Truncate(127)); line.Add("price", productPrice.FormatInvariant()); line.Add("currency", currencyCode); line.Add("sku", item.Item.Product.Sku.Truncate(50)); @@ -279,37 +278,6 @@ public static string GetApiUrl(bool sandbox) return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; } - public static Dictionary GetSecurityProtocols() - { - var dic = new Dictionary(); - - foreach (SecurityProtocolType protocol in Enum.GetValues(typeof(SecurityProtocolType))) - { - string friendlyName = null; - switch (protocol) - { - case SecurityProtocolType.Ssl3: - friendlyName = "SSL 3.0"; - break; - case SecurityProtocolType.Tls: - friendlyName = "TLS 1.0"; - break; - case SecurityProtocolType.Tls11: - friendlyName = "TLS 1.1"; - break; - case SecurityProtocolType.Tls12: - friendlyName = "TLS 1.2"; - break; - default: - friendlyName = protocol.ToString().ToUpper(); - break; - } - - dic.Add(protocol, friendlyName); - } - return dic; - } - public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString, bool isIpn = false) { try @@ -318,19 +286,15 @@ public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyStr return; string[] orderNoteStrings = T("Plugins.SmartStore.PayPal.OrderNoteStrings").Text.SplitSafe(";"); - var faviconUrl = "{0}Plugins/{1}/Content/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(false), Plugin.SystemName); - - var sb = new StringBuilder(); - sb.AppendFormat("", faviconUrl); - - var note = orderNoteStrings.SafeGet(0).FormatInvariant(anyString); - - sb.AppendFormat("{0}", note); + var faviconUrl = "{0}Plugins/{1}/Content/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(), Plugin.SystemName); + var note = $"" + orderNoteStrings.SafeGet(0).FormatInvariant(anyString); if (isIpn) + { order.HasNewPaymentNotification = true; + } - _orderService.AddOrderNote(order, sb.ToString()); + _orderService.AddOrderNote(order, note); } catch { } } @@ -517,9 +481,6 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa if (method.IsCaseInsensitiveEqual("GET") && data.HasValue()) url = url.EnsureEndsWith("?") + data; - if (settings.SecurityProtocol.HasValue) - ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; - var request = (HttpWebRequest)WebRequest.Create(url); request.Method = method; request.Accept = "application/json"; @@ -719,7 +680,7 @@ public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSett { session.AccessToken = (string)result.Json.access_token; - var expireSeconds = ((string)result.Json.expires_in).ToInt(5 * 60); + var expireSeconds = ((string)result.Json.expires_in).ToInt(30 * 60); session.TokenExpiration = DateTime.UtcNow.AddSeconds(expireSeconds); } else @@ -1204,7 +1165,21 @@ public PayPalSessionData() public string ApprovalUrl { get; set; } public Guid OrderGuid { get; private set; } public PayPalPaymentInstruction PaymentInstruction { get; set; } - } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendLine("SessionExpired: " + SessionExpired.ToString()); + sb.AppendLine("AccessToken: " + AccessToken.EmptyNull()); + sb.AppendLine("TokenExpiration: " + TokenExpiration.ToString()); + sb.AppendLine("PaymentId: " + PaymentId.EmptyNull()); + sb.AppendLine("PayerId: " + PayerId.EmptyNull()); + sb.AppendLine("ApprovalUrl: " + ApprovalUrl.EmptyNull()); + sb.AppendLine("OrderGuid: " + OrderGuid.ToString()); + sb.AppendLine("PaymentInstruction: " + (PaymentInstruction != null).ToString()); + return sb.ToString(); + } + } [Serializable] public class PayPalPaymentInstruction diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 1d92e8070b..878899e9a3 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Net; using SmartStore.Core.Configuration; namespace SmartStore.PayPal.Settings @@ -8,13 +7,10 @@ public abstract class PayPalSettingsBase { public PayPalSettingsBase() { - SecurityProtocol = SecurityProtocolType.Tls12; IpnChangesPaymentStatus = true; AddOrderNotes = true; } - public SecurityProtocolType? SecurityProtocol { get; set; } - public bool UseSandbox { get; set; } public bool AddOrderNotes { get; set; } @@ -109,10 +105,15 @@ public PayPalExpressPaymentSettings() public class PayPalPlusPaymentSettings : PayPalApiSettingsBase, ISettings { - /// - /// Specifies other payment methods to be offered in payment wall - /// - public List ThirdPartyPaymentMethods { get; set; } + public PayPalPlusPaymentSettings() + { + TransactMode = TransactMode.AuthorizeAndCapture; + } + + /// + /// Specifies other payment methods to be offered in payment wall + /// + public List ThirdPartyPaymentMethods { get; set; } /// /// Specifies whether to display the logo of a third party payment method diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index e75e60050d..0e8654e92c 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -66,8 +66,8 @@ ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll - - ..\..\packages\FluentValidation.6.4.1\lib\Net45\FluentValidation.dll + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -156,9 +156,6 @@ Settings.settings - - - True True diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalDirectPaymentInfoValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalDirectPaymentInfoValidator.cs deleted file mode 100644 index e0e7356110..0000000000 --- a/src/Plugins/SmartStore.PayPal/Validators/PayPalDirectPaymentInfoValidator.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FluentValidation; -using SmartStore.PayPal.Models; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Validators; - -namespace SmartStore.PayPal.Validators -{ - public class PaymentInfoValidator : AbstractValidator - { - public PaymentInfoValidator(ILocalizationService localizationService) { - //useful links: - //http://fluentvalidation.codeplex.com/wikipage?title=Custom&referringTitle=Documentation&ANCHOR#CustomValidator - //http://benjii.me/2010/11/credit-card-validator-attribute-for-asp-net-mvc-3/ - - RuleFor(x => x.CardholderName).NotEmpty().WithMessage(localizationService.GetResource("Payment.CardholderName.Required")); - RuleFor(x => x.CardNumber).IsCreditCard().WithMessage(localizationService.GetResource("Payment.CardNumber.Wrong")); - RuleFor(x => x.CardCode).Matches(@"^[0-9]{3,4}$").WithMessage(localizationService.GetResource("Payment.CardCode.Wrong")); - RuleFor(x => x.ExpireMonth).NotEmpty().WithMessage(localizationService.GetResource("Payment.ExpireMonth.Required")); - RuleFor(x => x.ExpireYear).NotEmpty().WithMessage(localizationService.GetResource("Payment.ExpireYear.Required")); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalExpressPaymentInfoValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalExpressPaymentInfoValidator.cs deleted file mode 100644 index 3290fdb4f7..0000000000 --- a/src/Plugins/SmartStore.PayPal/Validators/PayPalExpressPaymentInfoValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FluentValidation; -using SmartStore.PayPal.Models; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Validators; - -namespace SmartStore.PayPal.Validators -{ - public class PayPalExpressPaymentInfoValidator : AbstractValidator - { - public PayPalExpressPaymentInfoValidator(ILocalizationService localizationService) { - - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs deleted file mode 100644 index b7355b4299..0000000000 --- a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using FluentValidation; -using SmartStore.PayPal.Models; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Validators; - -namespace SmartStore.PayPal.Validators -{ - public class PayPalPlusConfigValidator : SmartValidatorBase - { - public PayPalPlusConfigValidator(ILocalizationService localize, Func addRule) - { - if (addRule("ClientId")) - { - RuleFor(x => x.ClientId).NotEmpty() - .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); - } - - if (addRule("Secret")) - { - RuleFor(x => x.Secret).NotEmpty() - .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); - } - - if (addRule("ThirdPartyPaymentMethods")) - { - RuleFor(x => x.ThirdPartyPaymentMethods) - .Must(x => x == null || x.Count <= 5) - .WithMessage(localize.GetResource("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); - } - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml index 50beba2a87..75e5f2624a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal.Settings; +@using SmartStore.PayPal.Models; @using SmartStore.Web.Framework; @model PayPalDirectConfigurationModel @{ @@ -37,18 +38,13 @@ @Html.SmartLabelFor(model => model.TransactMode)
    - - - - diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml index 4c66d8d184..a06aa3b1fd 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal.Settings; +@using SmartStore.PayPal.Models; @using SmartStore.Web.Framework; @using SmartStore.Web.Framework.UI; @model PayPalExpressConfigurationModel @@ -38,18 +39,13 @@ @Html.SmartLabelFor(model => model.TransactMode) - - - - @@ -146,7 +142,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/PaymentInfo.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/PaymentInfo.cshtml index 0ec7a9b830..fc5dd51d27 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/PaymentInfo.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/PaymentInfo.cshtml @@ -1,14 +1,39 @@ @model SmartStore.PayPal.Models.PayPalExpressPaymentInfoModel @{ Layout = ""; - Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); } @if (Model.CurrentPageIsBasket) { + + } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 793f2be499..9458875371 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -37,6 +37,8 @@ @using (Html.BeginForm()) { + @Html.HiddenFor(model => model.TransactMode) +
    + @Html.SmartLabelFor(model => model.DescriptionText) + + @Html.SettingEditorFor(model => model.DescriptionText, Html.TextAreaFor(model => model.DescriptionText, new { style = "height: 100px;" })) + @Html.ValidationMessageFor(model => model.DescriptionText) +
    + @Html.SmartLabelFor(model => model.PaymentMethodLogo) + + @Html.SettingEditorFor(model => model.PaymentMethodLogo, Html.EditorFor(m => m.PaymentMethodLogo, "Picture", new { transientUpload = true, validate = true })) + @Html.ValidationMessageFor(model => model.PaymentMethodLogo) +
    @Html.SmartLabelFor(model => model.TransactMode) - @Html.SettingOverrideCheckbox(model => model.TransactMode) - @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues, new { @class = "form-control noskin" }) + @Html.SettingEditorFor(model => model.TransactMode, Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues))
    - @Html.SettingOverrideCheckbox(model => model.ExcludedCreditCards) - @Html.ListBoxFor(x => x.ExcludedCreditCards, new MultiSelectList(Model.AvailableCreditCards, "Value", "Text"), new { multiple = "multiple" }) + @Html.SettingEditorFor(model => model.ExcludedCreditCards, + Html.ListBoxFor(model => model.ExcludedCreditCards, Model.AvailableCreditCards, new { multiple = "multiple", data_tags = "true" })) @Html.ValidationMessageFor(model => model.ExcludedCreditCards)
    - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    - @Html.SettingOverrideCheckbox(model => model.TransactMode) - @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues) + @Html.SettingEditorFor(model => model.TransactMode, Html.DropDownListFor(model => model.TransactMode, new List + { + new SelectListItem { Text = T("Plugins.Payments.PayPalDirect.ModeAuth"), Value = TransactMode.Authorize.ToString(), Selected = Model.TransactMode == TransactMode.Authorize }, + new SelectListItem { Text = T("Plugins.Payments.PayPalDirect.ModeAuthAndCapture"), Value = TransactMode.AuthorizeAndCapture.ToString(), Selected = Model.TransactMode == TransactMode.AuthorizeAndCapture } + }))
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -100,7 +96,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    - @Html.SettingOverrideCheckbox(model => model.TransactMode) - @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues) + @Html.SettingEditorFor(model => model.TransactMode, Html.DropDownListFor(model => model.TransactMode, new List + { + new SelectListItem { Text = T("Plugins.Payments.PayPalExpress.ModeAuth"), Value = TransactMode.Authorize.ToString(), Selected = Model.TransactMode == TransactMode.Authorize }, + new SelectListItem { Text = T("Plugins.Payments.PayPalExpress.ModeAuthAndCapture"), Value = TransactMode.AuthorizeAndCapture.ToString(), Selected = Model.TransactMode == TransactMode.AuthorizeAndCapture } + }))
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -137,7 +133,7 @@ @Html.SmartLabelFor(model => model.DefaultShippingPrice) - @Html.SettingEditorFor(model => model.DefaultShippingPrice) + @Html.SettingEditorFor(model => model.DefaultShippingPrice, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.DefaultShippingPrice)
    - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    @@ -71,8 +72,7 @@ @Html.SmartLabelFor(model => model.Secret) @@ -85,14 +85,6 @@ - - - - @@ -194,7 +185,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index cf5e9c02a4..0534131b2a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -31,14 +31,6 @@ @using (Html.BeginForm()) {
    @@ -61,8 +63,7 @@ @Html.SmartLabelFor(model => model.ClientId) - @Html.SettingOverrideCheckbox(model => model.ClientId) - @Html.TextBoxFor(model => model.ClientId) + @Html.SettingEditorFor(model => model.ClientId) @Html.ValidationMessageFor(model => model.ClientId)
    - @Html.SettingOverrideCheckbox(model => model.Secret) - @Html.TextBoxFor(model => model.Secret) + @Html.SettingEditorFor(model => model.Secret) @Html.ValidationMessageFor(model => model.Secret)
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    @Html.SmartLabelFor(model => model.ExperienceProfileId) @@ -101,7 +93,7 @@ @Html.SettingEditorFor(model => model.ExperienceProfileId) @T(Model.ExperienceProfileId.HasValue() ? "Common.Refresh" : "Common.AddNew") @@ -109,7 +101,7 @@ @if (Model.ExperienceProfileId.HasValue()) { - @T("Admin.Common.Delete") @@ -131,7 +123,7 @@ - @Html.SettingOverrideCheckbox(model => model.ThirdPartyPaymentMethods) - @Html.ListBoxFor(x => x.ThirdPartyPaymentMethods, - new MultiSelectList(Model.AvailableThirdPartyPaymentMethods, "Value", "Text"), - new { multiple = "multiple" }) + @Html.SettingEditorFor(model => model.ThirdPartyPaymentMethods, @
    + @Html.EditorFor(model => model.ThirdPartyPaymentMethods, "SelectList", new { multiple = "multiple", data_tags = "true", selectOptions = Model.AvailableThirdPartyPaymentMethods }) +
    ) @Html.ValidationMessageFor(model => model.ThirdPartyPaymentMethods)
    - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    - - - - @@ -72,10 +63,11 @@ - + @@ -136,7 +128,8 @@ @Html.SmartLabelFor(model => model.EnableIpn) @@ -154,8 +147,7 @@ @Html.SmartLabelFor(model => model.IpnUrl) @@ -168,17 +160,3 @@
    - @Html.SmartLabelFor(model => model.SecurityProtocol) - - @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) -
    @Html.SmartLabelFor(model => model.UseSandbox) @@ -63,8 +55,7 @@ @Html.SmartLabelFor(model => model.PdtToken) - @Html.SettingOverrideCheckbox(model => model.PdtToken) - @Html.TextBoxFor(model => model.PdtToken) + @Html.SettingEditorFor(model => model.PdtToken) @Html.ValidationMessageFor(model => model.PdtToken)
    @Html.SmartLabelFor(model => model.PdtValidateOrderTotal) - @Html.SettingEditorFor(model => model.PdtValidateOrderTotal) - @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal) - + @Html.SettingEditorFor(model => model.PdtValidateOrderTotal, + Html.CheckBoxFor(model => model.PdtValidateOrderTotal, new { data_toggler_for = "#PdtValidateOnlyWarnContainer" })) + @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal) +
    @@ -91,7 +83,7 @@ @Html.SmartLabelFor(model => model.AdditionalFee) - @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.SettingEditorFor(model => model.AdditionalFee, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.AdditionalFee)
    - @Html.SettingEditorFor(model => model.EnableIpn) + @Html.SettingEditorFor(model => model.EnableIpn, + Html.CheckBoxFor(model => model.EnableIpn, new { data_toggler_for = ".ipn-handling" })) @Html.ValidationMessageFor(model => model.EnableIpn)
    - @Html.SettingOverrideCheckbox(model => model.IpnUrl) - @Html.TextBoxFor(model => model.IpnUrl) + @Html.SettingEditorFor(model => model.IpnUrl) @Html.ValidationMessageFor(model => model.IpnUrl)
    } - - \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 9f641a1d08..0bb04e4692 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,6 +1,15 @@ #Release Notes +##Paypal 3.1.5.2 +###Improvements +* PayPal PLUS: additionally store access data in the database. + +##Paypal 3.1.5.1 +###Bugfixes +* PayPal Express: Checkout attributes were always ignored. + ##Paypal 3.0.0.3 +###Bugfixes * PayPal PLUS: Fixed #1200 Invalid request if the order amount is zero. "Amount cannot be zero" still occurred. ##Paypal 3.0.0.2 diff --git a/src/Plugins/SmartStore.PayPal/packages.config b/src/Plugins/SmartStore.PayPal/packages.config index 9fdcf4d14c..4b452fd305 100644 --- a/src/Plugins/SmartStore.PayPal/packages.config +++ b/src/Plugins/SmartStore.PayPal/packages.config @@ -2,7 +2,7 @@ - + diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index 827e457045..07c7595d0a 100644 --- a/src/Plugins/SmartStore.Shipping/Description.txt +++ b/src/Plugins/SmartStore.Shipping/Description.txt @@ -2,8 +2,8 @@ Description: Provides shipping methods for fixed rate shipping and computation based on weight. SystemName: SmartStore.Shipping Group: Shipping -Version: 3.0.3 -MinAppVersion: 3.0.0 +Version: 3.1.5 +MinAppVersion: 3.1.5 DisplayOrder: 1 FileName: SmartStore.Shipping.dll ResourceRootKey: Plugins.SmartStore.Shipping \ No newline at end of file diff --git a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj index 73b0d51d96..9ae457ef44 100644 --- a/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj +++ b/src/Plugins/SmartStore.Shipping/SmartStore.Shipping.csproj @@ -90,11 +90,9 @@
    ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll - True ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll - True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll diff --git a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml index 5359b3178d..d96bda0ff5 100644 --- a/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml +++ b/src/Plugins/SmartStore.Shipping/Views/ByTotal/Configure.cshtml @@ -131,28 +131,6 @@ ", + "
    ".FormatInvariant(elementId, siteKey), + "".FormatInvariant(url), + }.StrJoin(""); + + return MvcHtmlString.Create(script); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs new file mode 100644 index 0000000000..48f56031a8 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Captcha/ValidateCaptchaAttribute.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Security +{ + public class ValidateCaptchaAttribute : ActionFilterAttribute + { + public ValidateCaptchaAttribute() + { + Logger = NullLogger.Instance; + } + + public Lazy CaptchaSettings { get; set; } + public ILogger Logger { get; set; } + public Lazy LocalizationService { get; set; } + + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + var valid = false; + + try + { + var captchaSettings = CaptchaSettings.Value; + if (captchaSettings.Enabled && captchaSettings.ReCaptchaPrivateKey.HasValue()) + { + var verifyUrl = CommonHelper.GetAppSetting("g:RecaptchaVerifyUrl"); + var recaptchaResponse = filterContext.HttpContext.Request.Form["g-recaptcha-response"]; + + var url = "{0}?secret={1}&response={2}".FormatInvariant( + verifyUrl, + HttpUtility.UrlEncode(captchaSettings.ReCaptchaPrivateKey), + HttpUtility.UrlEncode(recaptchaResponse) + ); + + using (var client = new WebClient()) + { + var jsonResponse = client.DownloadString(url); + using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonResponse))) + { + var serializer = new DataContractJsonSerializer(typeof(GoogleRecaptchaApiResponse)); + var result = serializer.ReadObject(memoryStream) as GoogleRecaptchaApiResponse; + + if (result == null) + { + Logger.Error(LocalizationService.Value.GetResource("Common.CaptchaUnableToVerify")); + } + else + { + if (result.ErrorCodes == null) + { + valid = result.Success; + } + } + } + } + } + } + catch (Exception exception) + { + Logger.ErrorsAll(exception); + } + + // this will push the result value into a parameter in our Action + filterContext.ActionParameters["captchaValid"] = valid; + + base.OnActionExecuting(filterContext); + } + } + + + [DataContract] + public class GoogleRecaptchaApiResponse + { + [DataMember(Name = "success")] + public bool Success { get; set; } + + [DataMember(Name = "error-codes")] + public List ErrorCodes { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs new file mode 100644 index 0000000000..c9e58fe770 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/Honeypot.cs @@ -0,0 +1,76 @@ +using System; +using System.Text; +using System.Web; +using System.Web.Security; +using Newtonsoft.Json; +using SmartStore.Utilities; + +namespace SmartStore.Web.Framework.Security +{ + public class HoneypotField + { + public string Name { get; set; } + public DateTime CreatedOnUtc { get; set; } + } + + internal static class Honeypot + { + internal const string TokenFieldName = "__hpToken"; + + private static readonly string[] _fieldNames = new[] { "Phone", "Fax", "Email", "Age", "Name", "FirstName", "LastName", "Type", "Custom", "Reason", "Pet", "Question", "Region" }; + private static readonly string _fieldSuffix = CommonHelper.GenerateRandomDigitCode(5); + + public static HoneypotField CreateToken() + { + var r = new Random(); + + // Create a rondom field name with pattern "[random1]-[random2][suffix]" + var len = _fieldNames.Length; + var fieldName = string.Concat(_fieldNames[r.Next(0, len)], "-", _fieldNames[r.Next(0, len)], _fieldSuffix); + + return new HoneypotField + { + Name = fieldName, + CreatedOnUtc = DateTime.UtcNow + }; + } + + public static string SerializeToken(HoneypotField token) + { + Guard.NotNull(token, nameof(token)); + + var json = JsonConvert.SerializeObject(token); + var encoded = MachineKey.Protect(Encoding.UTF8.GetBytes(json)); + + var result = Convert.ToBase64String(encoded); + return result; + } + + public static HoneypotField DeserializeToken(string token) + { + Guard.NotEmpty(token, nameof(token)); + + var encoded = Convert.FromBase64String(token); + var decoded = MachineKey.Unprotect(encoded); + var json = Encoding.UTF8.GetString(decoded); + + var result = JsonConvert.DeserializeObject(json); + return result; + } + + public static bool IsBot(HttpContextBase httpContext) + { + var tokenString = httpContext.Request.Form[TokenFieldName]; + if (tokenString.IsEmpty()) + { + throw new InvalidOperationException("The required honeypot form field is missing. Please render the field with 'Html.HoneypotField()'."); + } + + var token = DeserializeToken(tokenString); + var trap = httpContext.Request.Form[token.Name]; + var isBot = trap == null || trap.Length > 0 || (DateTime.UtcNow - token.CreatedOnUtc).TotalMilliseconds < 2000; + + return isBot; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs new file mode 100644 index 0000000000..49593920a1 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/HtmlHoneypotExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Web.Mvc; +using System.Web.Mvc.Html; + +namespace SmartStore.Web.Framework.Security +{ + public static class HtmlHoneypotExtensions + { + public static MvcHtmlString HoneypotField(this HtmlHelper html) + { + var token = Honeypot.CreateToken(); + var serializedToken = Honeypot.SerializeToken(token); + + var textField = html.TextBox(token.Name, string.Empty, new { @class = "required-text-input", autocomplete = "off" }).ToHtmlString(); + var hiddenField = html.Hidden(Honeypot.TokenFieldName, serializedToken).ToHtmlString(); + + return MvcHtmlString.Create(textField + hiddenField); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs new file mode 100644 index 0000000000..3d026fc75f --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Security/Honeypot/ValidateHoneypotAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; + +namespace SmartStore.Web.Framework.Security +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] + public class ValidateHoneypotAttribute : FilterAttribute, IAuthorizationFilter + { + public ValidateHoneypotAttribute() + { + Logger = NullLogger.Instance; + } + + public SecuritySettings SecuritySettings { get; set; } + public ILogger Logger { get; set; } + public Localizer T { get; set; } + public Lazy WebHelper { get; set; } + + public void OnAuthorization(AuthorizationContext filterContext) + { + if (!SecuritySettings.EnableHoneypotProtection) + return; + + var isBot = Honeypot.IsBot(filterContext.HttpContext); + if (!isBot) + return; + + Logger.Warn("Honeypot detected a bot and rejected the request."); + + var redirectUrl = WebHelper.Value.GetThisPageUrl(true); + filterContext.Result = new RedirectResult(redirectUrl); + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs index 20ca8336ee..4fa6c14d76 100644 --- a/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Security/RequireHttpsByConfigAttribute.cs @@ -40,8 +40,10 @@ public virtual void OnAuthorization(AuthorizationContext filterContext) var webHelper = WebHelper.Value; var currentConnectionSecured = webHelper.IsCurrentConnectionSecured(); - - if (securitySettings.ForceSslForAllPages) + var storeContext = StoreContext.Value; + var currentStore = storeContext.CurrentStore; + + if (currentStore.ForceSslForAllPages) { // all pages are forced to be SSL no matter of the specified value this.SslRequirement = SslRequirement.Yes; @@ -53,9 +55,6 @@ public virtual void OnAuthorization(AuthorizationContext filterContext) { if (!currentConnectionSecured) { - var storeContext = StoreContext.Value; - var currentStore = storeContext.CurrentStore; - if (currentStore != null && currentStore.GetSecurityMode() > HttpSecurityMode.Unsecured) { // redirect to HTTPS version of page diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs index 6ea897dfa0..b81cef572d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRoute.cs @@ -1,5 +1,9 @@ -using System.Web; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; using System.Web.Routing; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; @@ -13,9 +17,11 @@ namespace SmartStore.Web.Framework.Seo ///
    public class GenericPathRoute : LocalizedRoute { - #region Constructors - - /// + // Key = Prefix, Value = EntityType + private readonly Multimap _urlPrefixes = + new Multimap(StringComparer.OrdinalIgnoreCase, x => new HashSet(x, StringComparer.OrdinalIgnoreCase)); + + /// /// Initializes a new instance of the System.Web.Routing.Route class, using the specified URL pattern and handler class. /// /// The URL pattern for the route. @@ -62,9 +68,12 @@ public GenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDic { } - #endregion + public void RegisterUrlPrefix(string prefix, params string[] entityNames) + { + Guard.NotEmpty(prefix, nameof(prefix)); - #region Methods + _urlPrefixes.AddRange(prefix, entityNames); + } /// /// Returns information about the requested route. @@ -76,78 +85,104 @@ public GenericPathRoute(string url, RouteValueDictionary defaults, RouteValueDic public override RouteData GetRouteData(HttpContextBase httpContext) { RouteData data = base.GetRouteData(httpContext); + if (data != null && DataSettings.DatabaseIsInstalled()) { - var urlRecordService = EngineContext.Current.Resolve(); var slug = data.Values["generic_se_name"] as string; - var urlRecord = urlRecordService.GetBySlug(slug); + + if (TryResolveUrlPrefix(slug, out var urlPrefix, out var actualSlug, out var entityNames)) + { + slug = actualSlug; + } + + var urlRecordService = EngineContext.Current.Resolve(); + var urlRecord = urlRecordService.GetBySlug(slug); if (urlRecord == null) { - //no URL record found - data.Values["controller"] = "Error"; - data.Values["action"] = "NotFound"; - return data; + // no URL record found + return NotFound(data); } + if (!urlRecord.IsActive) { // URL record is not active. let's find the latest one var activeSlug = urlRecordService.GetActiveSlug(urlRecord.EntityId, urlRecord.EntityName, urlRecord.LanguageId); - if (!string.IsNullOrWhiteSpace(activeSlug)) + if (activeSlug.HasValue()) { - // the active one is found + // The active one is found var webHelper = EngineContext.Current.Resolve(); var response = httpContext.Response; response.Status = "301 Moved Permanently"; + if (urlPrefix.HasValue()) + { + activeSlug = urlPrefix + "/" + activeSlug; + } response.RedirectLocation = string.Format("{0}{1}", webHelper.GetStoreLocation(false), activeSlug); response.End(); return null; } else { - // no active slug found - data.Values["controller"] = "Error"; - data.Values["action"] = "NotFound"; - return data; - } + // no active slug found + return NotFound(data); + } } - // process URL - data.Values["SeName"] = urlRecord.Slug; + // Verify prefix matches any assigned entity name + if (entityNames != null && !entityNames.Contains(urlRecord.EntityName)) + { + // does NOT match + return NotFound(data); + } + + // process URL + data.DataTokens["UrlRecord"] = urlRecord; + data.Values["SeName"] = slug; + + string controller, action, paramName; + switch (urlRecord.EntityName.ToLowerInvariant()) { case "product": { - data.Values["controller"] = "Product"; - data.Values["action"] = "ProductDetails"; - data.Values["productid"] = urlRecord.EntityId; + controller = "Product"; + action = "ProductDetails"; + paramName = "productid"; } break; case "category": { - data.Values["controller"] = "Catalog"; - data.Values["action"] = "Category"; - data.Values["categoryid"] = urlRecord.EntityId; + controller = "Catalog"; + action = "Category"; + paramName = "categoryid"; } break; case "manufacturer": { - data.Values["controller"] = "Catalog"; - data.Values["action"] = "Manufacturer"; - data.Values["manufacturerid"] = urlRecord.EntityId; + controller = "Catalog"; + action = "Manufacturer"; + paramName = "manufacturerid"; } break; - case "newsitem": + case "topic": + { + controller = "Topic"; + action = "TopicDetails"; + paramName = "topicId"; + } + break; + case "newsitem": { - data.Values["controller"] = "News"; - data.Values["action"] = "NewsItem"; - data.Values["newsItemId"] = urlRecord.EntityId; + controller = "News"; + action = "NewsItem"; + paramName = "newsItemId"; } break; case "blogpost": { - data.Values["controller"] = "Blog"; - data.Values["action"] = "BlogPost"; - data.Values["blogPostId"] = urlRecord.EntityId; + controller = "Blog"; + action = "BlogPost"; + paramName = "blogPostId"; } break; default: @@ -155,10 +190,46 @@ public override RouteData GetRouteData(HttpContextBase httpContext) throw new SmartException(string.Format("Unsupported EntityName for UrlRecord: {0}", urlRecord.EntityName)); } } - } + + data.Values["controller"] = controller; + data.Values["action"] = action; + data.Values[paramName] = urlRecord.EntityId; + } + return data; } - #endregion + private RouteData NotFound(RouteData data) + { + data.Values["controller"] = "Error"; + data.Values["action"] = "NotFound"; + + return data; + } + + private bool TryResolveUrlPrefix(string slug, out string urlPrefix, out string actualSlug, out ICollection entityNames) + { + urlPrefix = null; + actualSlug = null; + entityNames = null; + + if (_urlPrefixes.Count > 0) + { + var firstSepIndex = slug.IndexOf('/'); + if (firstSepIndex > 0) + { + var prefix = slug.Substring(0, firstSepIndex); + if (_urlPrefixes.ContainsKey(prefix)) + { + urlPrefix = prefix; + entityNames = _urlPrefixes[prefix]; + actualSlug = slug.Substring(prefix.Length + 1); + return true; + } + } + } + + return false; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs index 9029891c5f..cf931f15bc 100644 --- a/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Seo/GenericPathRouteExtensions.cs @@ -6,27 +6,31 @@ namespace SmartStore.Web.Framework.Seo { public static class GenericPathRouteExtensions { - //Override for generic route public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url) { return MapGenericPathRoute(routes, name, url, null /* defaults */, (object)null /* constraints */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults) { return MapGenericPathRoute(routes, name, url, defaults, (object)null /* constraints */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints) { return MapGenericPathRoute(routes, name, url, defaults, constraints, null /* namespaces */); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, string[] namespaces) { return MapGenericPathRoute(routes, name, url, null /* defaults */, null /* constraints */, namespaces); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces) { return MapGenericPathRoute(routes, name, url, defaults, null /* constraints */, namespaces); } + public static Route MapGenericPathRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) { if (routes == null) @@ -54,5 +58,30 @@ public static Route MapGenericPathRoute(this RouteCollection routes, string name return route; } + + /// + /// Changes the url pattern of an existing named route. + /// + /// Route collection + /// Name of the route + /// The new url pattern + /// The route instance + public static Route ChangeRouteUrl(this RouteCollection routes, string name, string url) + { + Guard.NotNull(routes, nameof(routes)); + Guard.NotEmpty(name, nameof(name)); + Guard.NotEmpty(url, nameof(url)); + + var route = routes[name] as Route; + + if (route == null) + { + throw new ArgumentException("The route '{0}' does not exist or is not assignable from 'Route'.".FormatInvariant(name), nameof(name)); + } + + route.Url = url; + + return route; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs index 107bc2e8ff..278679dbde 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/SaveSettingAttribute.cs @@ -41,34 +41,36 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) public override void OnActionExecuted(ActionExecutedContext filterContext) { - if (_settingsWriteBatch != null) + if (filterContext.Controller.ViewData.ModelState.IsValid) { - _settingsWriteBatch.Dispose(); - _settingsWriteBatch = null; - } - - if (!filterContext.Controller.ViewData.ModelState.IsValid) - { - return; - } + var updateSettings = true; + var redirectResult = filterContext.Result as RedirectToRouteResult; + if (redirectResult != null) + { + var controllerName = redirectResult.RouteValues["controller"] as string; + var areaName = redirectResult.RouteValues["area"] as string; + if (controllerName.IsCaseInsensitiveEqual("security") && areaName.IsCaseInsensitiveEqual("admin")) + { + // Insufficient permission. We must not save because the action did not run. + updateSettings = false; + } + } - var redirectResult = filterContext.Result as RedirectToRouteResult; - if (redirectResult != null) - { - var controllerName = redirectResult.RouteValues["controller"] as string; - var areaName = redirectResult.RouteValues["area"] as string; - if (controllerName.IsCaseInsensitiveEqual("security") && areaName.IsCaseInsensitiveEqual("admin")) + if (updateSettings) { - // Insufficient permission. Get outta here, because the action did not run. We must not save. - return; + var settingHelper = new StoreDependingSettingHelper(filterContext.Controller.ViewData); + + foreach (var param in _settingParams) + { + settingHelper.UpdateSettings(param.Instance, _form, _storeId, Services.Settings); + } } } - var settingHelper = new StoreDependingSettingHelper(filterContext.Controller.ViewData); - - foreach (var param in _settingParams) + if (_settingsWriteBatch != null) { - settingHelper.UpdateSettings(param.Instance, _form, _storeId, Services.Settings); + _settingsWriteBatch.Dispose(); + _settingsWriteBatch = null; } base.OnActionExecuted(filterContext); diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs index d68abb9389..7605dd7934 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Web.Mvc; using SmartStore.ComponentModel; -using SmartStore.Core.Configuration; using SmartStore.Core.Infrastructure; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; @@ -19,7 +18,7 @@ public StoreDependingSettingHelper(ViewDataDictionary viewData) _viewData = viewData; } - public static string ViewDataKey { get { return "StoreDependingSettingData"; } } + public static string ViewDataKey => "StoreDependingSettingData"; public StoreDependingSettingData Data { @@ -49,6 +48,11 @@ public bool IsOverrideChecked(object settings, string name, FormCollection form) public void AddOverrideKey(object settings, string name) { + if (Data == null) + { + throw new SmartException("You must call GetOverrideKeys or CreateViewDataObject before AddOverrideKey."); + } + var key = settings.GetType().Name + "." + name; Data.OverrideSettingKeys.Add(key); } @@ -95,25 +99,31 @@ public void GetOverrideKeys( continue; } - var key = string.Empty; - var setting = string.Empty; - - if (localized == null) - { - key = settingName + "." + name; - setting = settingService.GetSettingByKey(key, storeId: storeId); - } - else - { - key = string.Concat("Locales[", index.ToString(), "].", name); - setting = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); - } - - if (!string.IsNullOrEmpty(setting)) + string key = null; + + if (localized == null) + { + key = settingName + "." + name; + + if (settingService.GetSettingByKey(key, storeId: storeId) == null) + { + key = null; + } + } + else + { + var value = localizedEntityService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); + if (!string.IsNullOrEmpty(value)) + { + key = string.Concat("Locales[", index.ToString(), "].", name); + } + } + + if (key != null) { data.OverrideSettingKeys.Add(key); } - } + } if (isRootModel) { @@ -139,13 +149,14 @@ public void GetOverrideKey( return; } - var data = Data ?? new StoreDependingSettingData(); - var setting = string.Empty; + var key = formKey; if (localized == null) { - var key = string.Concat(settings.GetType().Name, ".", settingName); - setting = settingService.GetSettingByKey(key, storeId: storeId); + if (settingService.GetSettingByKey(string.Concat(settings.GetType().Name, ".", settingName), storeId: storeId) == null) + { + key = null; + } } else { @@ -153,33 +164,57 @@ public void GetOverrideKey( throw new ArgumentException("Localized override key not supported yet."); } - if (!string.IsNullOrEmpty(setting)) + if (key != null) { - data.OverrideSettingKeys.Add(formKey); + var data = Data ?? new StoreDependingSettingData(); + data.OverrideSettingKeys.Add(key); } } - public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized = null) + /// + /// Updates settings for a store. + /// + /// Settings class instance. + /// Form value collection. + /// Store identifier. + /// Setting service. + /// Localized model. + /// Function to map property names. Return null to skip a property. + public void UpdateSettings( + object settings, + FormCollection form, + int storeId, + ISettingService settingService, + ILocalizedModelLocal localized = null, + Func propertyNameMapper = null) { var settingName = settings.GetType().Name; var properties = FastProperty.GetProperties(localized == null ? settings.GetType() : localized.GetType()).Values; - using (settingService.BeginScope()) + foreach (var prop in properties) { - foreach (var prop in properties) + var name = prop.Name; + + if (propertyNameMapper != null) { - var name = prop.Name; - var key = settingName + "." + name; + name = propertyNameMapper(name); + } - if (storeId == 0 || IsOverrideChecked(key, form)) - { - dynamic value = prop.GetValue(localized == null ? settings : localized); - settingService.SetSetting(key, value == null ? "" : value, storeId, false); - } - else if (storeId > 0) - { - settingService.DeleteSetting(key, storeId); - } + if (string.IsNullOrWhiteSpace(name)) + { + continue; + } + + var key = string.Concat(settingName, ".", name); + + if (storeId == 0 || IsOverrideChecked(key, form)) + { + dynamic value = prop.GetValue(localized == null ? settings : localized); + settingService.SetSetting(key, value == null ? "" : value, storeId, false); + } + else if (storeId > 0) + { + settingService.DeleteSetting(key, storeId); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 8ef73ec72c..b001dafaef 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -92,10 +92,6 @@ ..\..\packages\BundleTransformer.Core.1.9.152\lib\net40\BundleTransformer.Core.dll True - - ..\..\packages\BundleTransformer.Less.1.9.143\lib\net40\BundleTransformer.Less.dll - True - ..\..\packages\BundleTransformer.SassAndScss.1.9.154\lib\net40\BundleTransformer.SassAndScss.dll True @@ -111,9 +107,8 @@ ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll True - - ..\..\packages\FluentValidation.6.4.1\lib\Net45\FluentValidation.dll - True + + ..\..\packages\FluentValidation.7.4.0\lib\net45\FluentValidation.dll ..\..\packages\JavaScriptEngineSwitcher.Core.2.4.0\lib\net45\JavaScriptEngineSwitcher.Core.dll @@ -241,13 +236,20 @@ + + + + + + + @@ -320,6 +322,7 @@ + @@ -388,6 +391,7 @@ + @@ -404,9 +408,9 @@ - - - + + + @@ -439,8 +443,7 @@ - - + diff --git a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs index 65a3189792..26e9c264c2 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs +++ b/src/Presentation/SmartStore.Web.Framework/SmartUrlRoutingModule.cs @@ -151,10 +151,10 @@ public virtual void PostAuthorizeRequest(HttpContextBase context) if (request == null) return; - if (IsPluginPath(request) && WebHelper.IsStaticResourceRequested(request)) + if (IsExtensionPath(request) && WebHelper.IsStaticResourceRequested(request)) { - // We're in debug mode and in dev environment, so we can be sure that 'PluginDebugViewVirtualPathProvider' is running - var file = HostingEnvironment.VirtualPathProvider.GetFile(request.AppRelativeCurrentExecutionFilePath) as DebugPluginVirtualFile; + // We're in debug mode and in dev environment + var file = HostingEnvironment.VirtualPathProvider.GetFile(request.AppRelativeCurrentExecutionFilePath) as DebugVirtualFile; if (file != null) { context.Items["DebugFile"] = file; @@ -169,7 +169,7 @@ public virtual void PreSendRequestHeaders(HttpContextBase context) if (context?.Response == null) return; - var file = context.Items?["DebugFile"] as DebugPluginVirtualFile; + var file = context.Items?["DebugFile"] as DebugVirtualFile; if (file != null) { context.Response.AddFileDependency(file.PhysicalPath); @@ -202,9 +202,10 @@ public virtual void PostResolveRequestCache(HttpContextBase context) } } - private bool IsPluginPath(HttpRequestBase request) + private bool IsExtensionPath(HttpRequestBase request) { - var result = request.AppRelativeCurrentExecutionFilePath.StartsWith("~/Plugins/", StringComparison.InvariantCultureIgnoreCase); + var path = request.AppRelativeCurrentExecutionFilePath.ToLower(); + var result = path.StartsWith("~/plugins/") || path.StartsWith("~/themes/"); return result; } diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs index f729518602..259f6ef028 100644 --- a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/Drops/ObjectDrop.cs @@ -1,5 +1,6 @@ using System; using System.Reflection; +using System.Runtime.Serialization; using SmartStore.ComponentModel; using SmartStore.Core; @@ -36,7 +37,9 @@ protected override object InvokeMember(string name) var prop = FastProperty.GetProperty(_type, name); if (prop != null) { - return prop.GetValue(_data); + return prop.Property.HasAttribute(true) + ? null + : prop.GetValue(_data); } var method = _type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public); diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs index 7175b83841..9f3b03baca 100644 --- a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidTemplateEngine.cs @@ -12,6 +12,7 @@ using SmartStore.Core.Localization; using SmartStore.Core.Themes; using SmartStore.Services; +using SmartStore.Utilities; namespace SmartStore.Templating.Liquid { @@ -32,7 +33,18 @@ public LiquidTemplateEngine( _vpp = vpp; _localizer = localizer; _themeContext = themeContext; - + + // Register Value type transformers + var allowedMoneyProps = new[] + { + TypeHelper.NameOf(x => x.Amount), + TypeHelper.NameOf(x => x.RoundedAmount), + TypeHelper.NameOf(x => x.TruncatedAmount), + TypeHelper.NameOf(x => x.Formatted), + TypeHelper.NameOf(x => x.DecimalDigits) + }; + Template.RegisterSafeType(typeof(Money), allowedMoneyProps, x => x); + // Register tag "zone" Template.RegisterTagFactory(new ZoneTagFactory(_services.Value.EventPublisher)); diff --git a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs index 541af87685..9363346814 100644 --- a/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs +++ b/src/Presentation/SmartStore.Web.Framework/Templating/Liquid/LiquidUtil.cs @@ -16,7 +16,7 @@ internal static object CreateSafeObject(object value) return null; } - if (value is TestDrop) + if (value is TestDrop || value is IFormattable) { return value; } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs index 8ebc50a0d2..1e3e3fbd6c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/AssetTranslator.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using BundleTransformer.Core; using BundleTransformer.Core.Assets; using BundleTransformer.Core.Constants; -using BundleTransformer.Core.Transformers; using BundleTransformer.Core.Translators; using BundleTransformer.SassAndScss.Translators; using SmartStore.Core.Infrastructure; @@ -152,12 +150,4 @@ protected override string[] ValidTypeCodes get { return new[] { "sass", "scss" }; } } } - - public sealed class LessTranslator : AssetTranslator - { - protected override string[] ValidTypeCodes - { - get { return new[] { "less" }; } - } - } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs index 5aff8064f7..af308fdb66 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/BundlingVirtualPathProvider.cs @@ -43,8 +43,20 @@ public override VirtualFile GetFile(string virtualPath) return base.GetFile(virtualPath); } - - public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) + + public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) + { + var styleResult = ThemeHelper.IsStyleSheet(virtualPath); + if (styleResult.IsPreprocessor && !(styleResult.IsThemeVars || styleResult.IsModuleImports) && virtualPathDependencies != null) + { + // Exclude the special imports from the file dependencies list + return base.GetFileHash(virtualPath, ThemeHelper.RemoveVirtualImports(virtualPathDependencies.Cast())); + } + + return base.GetFileHash(virtualPath, virtualPathDependencies); + } + + public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { var styleResult = ThemeHelper.IsStyleSheet(virtualPath); @@ -87,7 +99,7 @@ public override CacheDependency GetCacheDependency(string virtualPath, IEnumerab // invalidate the cache when variables change cacheKey = FrameworkCacheConsumer.BuildThemeVarsCacheKey(theme.ThemeName, storeId); - if ((styleResult.IsSass || styleResult.IsLess) && (ThemeHelper.IsStyleValidationRequest())) + if (styleResult.IsSass && (ThemeHelper.IsStyleValidationRequest())) { // Special case: ensure that cached validation result gets nuked in a while, // when ThemeVariableService publishes the entity changed messages. @@ -95,8 +107,10 @@ public override CacheDependency GetCacheDependency(string virtualPath, IEnumerab } } + var files = ThemingVirtualPathProvider.MapDependencyPaths(fileDependencies); + return new CacheDependency( - ThemingVirtualPathProvider.MapDependencyPaths(fileDependencies), + files, cacheKey == null ? new string[0] : new string[] { cacheKey }, utcStart); } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs index d34e746c76..a834f3cfd6 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/DefaultAssetCache.cs @@ -169,7 +169,7 @@ public CachedAssetEntry InsertAsset(string virtualPath, IEnumerable virt CreateFileFromEntries(cacheDirectoryName, "asset.dependencies", deps); // Save hash file - var currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, ThemeHelper.RemoveVirtualImports(deps)); + var currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, deps); CreateFileFromEntries(cacheDirectoryName, "asset.hash", new[] { currentHash }); // Save codes file @@ -344,7 +344,7 @@ private bool TryValidate(string virtualPath, string lastDeps, string lastHash, o parsedDeps = ParseFileContent(lastDeps); // Check if dependency files hash matches the last saved hash - currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, ThemeHelper.RemoveVirtualImports(parsedDeps)); + currentHash = BundleTable.VirtualPathProvider.GetFileHash(virtualPath, parsedDeps); return lastHash == currentHash; } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs index fb59f5e743..720b884ee4 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/Assets/ModuleImportsVirtualFile.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Web.Hosting; using SmartStore.Core.Plugins; +using SmartStore.Core.Data; namespace SmartStore.Web.Framework.Theming.Assets { @@ -17,7 +18,10 @@ static ModuleImportsVirtualFile() _adminImports = new HashSet(); _publicImports = new HashSet(); - CollectModuleImports(); + if (DataSettings.DatabaseIsInstalled()) + { + CollectModuleImports(); + } } private static void CollectModuleImports() diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs index 00c6d28f40..3a1cc2ac7c 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/CssHttpHandler.cs @@ -23,14 +23,6 @@ protected override IAsset TranslateAssetCore(IAsset asset, ITransformer transfor } } - public class LessCssHttpHandler : CssHttpHandlerBase - { - protected override IAsset TranslateAssetCore(IAsset asset, ITransformer transformer, bool isDebugMode) - { - return InnerTranslateAsset("LessTranslator", asset, transformer, isDebugMode); - } - } - public abstract class CssHttpHandlerBase : BundleTransformer.Core.HttpHandlers.StyleAssetHandlerBase { protected CssHttpHandlerBase() @@ -134,7 +126,7 @@ protected override IAsset TranslateAsset(IAsset asset, ITransformer transformer, _context.Response.StatusCode = 500; _context.Response.End(); } - + throw; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs b/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs index 591aa577e1..1ea6b7484b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/DefaultThemeFileResolver.cs @@ -139,11 +139,9 @@ public InheritedThemeFileResult Resolve(string virtualPath) Func nullOrFile = () => { - if (isExplicit) - { - return new InheritedThemeFileResult { IsExplicit = true, OriginalVirtualPath = virtualPath, Query = query }; - } - return null; + return isExplicit + ? new InheritedThemeFileResult { IsExplicit = true, OriginalVirtualPath = virtualPath, Query = query } + : null; }; ThemeManifest currentTheme = ResolveTheme(requestedThemeName, relativePath, query, out isExplicit); @@ -162,8 +160,13 @@ public InheritedThemeFileResult Resolve(string virtualPath) return nullOrFile(); } } + else if (isExplicit && currentTheme.BaseTheme != null) + { + // A file from the base theme has been requested + currentTheme = currentTheme.BaseTheme; + } - var fileKey = new FileKey(currentTheme.ThemeName, relativePath); + var fileKey = new FileKey(currentTheme.ThemeName, relativePath, query); InheritedThemeFileResult result; using (_rwLock.GetUpgradeableReadLock()) @@ -185,6 +188,7 @@ public InheritedThemeFileResult Resolve(string virtualPath) ResultPhysicalPath = resultPhysicalPath, OriginalThemeName = requestedThemeName, ResultThemeName = actualLocation, + IsExplicit = isExplicit, Query = query }; } @@ -205,7 +209,7 @@ private ThemeManifest ResolveTheme(string requestedThemeName, string relativePat isExplicit = false; ThemeManifest currentTheme; - var isAdmin = EngineContext.Current.Resolve().IsAdmin; // ThemeHelper.IsAdminArea() + var isAdmin = EngineContext.Current.Resolve().IsAdmin; if (isAdmin) { currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); @@ -231,10 +235,12 @@ private ThemeManifest ResolveTheme(string requestedThemeName, string relativePat } } + currentTheme = ThemeHelper.ResolveCurrentTheme(); + if (isPreprocessor && query != null && query.StartsWith("explicit", StringComparison.OrdinalIgnoreCase)) { - // special case to support SASS/LESS @import declarations - // within inherited SASS/LESS files. Snenario: an inheritor wishes to + // special case to support SASS @import declarations + // within inherited SASS files. Snenario: an inheritor wishes to // include the same file from it's base theme (e.g. custom.scss) just to tweak it // a bit for his child theme. Without the 'explicit' query the resolution starting point // for custom.scss would be the CURRENT theme's folder, and NOT the requested one's, @@ -242,10 +248,6 @@ private ThemeManifest ResolveTheme(string requestedThemeName, string relativePat currentTheme = _themeRegistry.GetThemeManifest(requestedThemeName); isExplicit = true; } - else - { - currentTheme = ThemeHelper.ResolveCurrentTheme(); - } } return currentTheme; @@ -280,10 +282,10 @@ private string LocateFile(string themeName, string relativePath, out string virt } - private class FileKey : Tuple + private class FileKey : Tuple { - public FileKey(string themeName, string relativePath) - : base(themeName.ToLower(), relativePath.ToLower()) + public FileKey(string themeName, string relativePath, string query) + : base(themeName.ToLower(), relativePath.ToLower(), query?.ToLower()) { } @@ -296,6 +298,11 @@ public string RelativePath { get { return base.Item2; } } + + public string Query + { + get { return base.Item3; } + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs b/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs index b7f2d0757b..37bb50a4b2 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/InheritedVirtualThemeFile.cs @@ -7,17 +7,22 @@ namespace SmartStore.Web.Framework.Theming { internal class InheritedVirtualThemeFile : VirtualFile { - private readonly InheritedThemeFileResult _resolveResult; - public InheritedVirtualThemeFile(InheritedThemeFileResult resolveResult) : base(DetermineVirtualPath(resolveResult)) { - this._resolveResult = resolveResult; + ResolveResult = resolveResult; } - public override Stream Open() + public InheritedThemeFileResult ResolveResult { get; } + + public string ResultVirtualPath + { + get { return ResolveResult.ResultVirtualPath ?? ResolveResult.OriginalVirtualPath; } + } + + public override Stream Open() { - return new FileStream(_resolveResult.ResultPhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return new FileStream(ResolveResult.ResultPhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } private static string DetermineVirtualPath(InheritedThemeFileResult resolveResult) diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs index 88d06542ba..0c9232dfb6 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/SmartVirtualPathProvider.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Web; @@ -8,11 +7,13 @@ using SmartStore.Core.Infrastructure; using SmartStore.Utilities.Threading; using SmartStore.Utilities; +using SmartStore.Core.Themes; namespace SmartStore.Web.Framework.Theming { public abstract class SmartVirtualPathProvider : VirtualPathProvider { + private readonly IThemeRegistry _themeRegistry; private readonly Dictionary _cachedDebugFilePaths = new Dictionary(); private readonly ContextState> _requestState = new ContextState>("PluginDebugViewVPP.RequestCache", () => new Dictionary()); private readonly DirectoryInfo _pluginsDebugDir; @@ -28,14 +29,17 @@ static SmartVirtualPathProvider() protected SmartVirtualPathProvider() { var appRootPath = HostingEnvironment.MapPath("~/").EnsureEndsWith("\\"); - var debugPath = Path.GetFullPath(Path.Combine(appRootPath, @"..\..\Plugins")); - if (Directory.Exists(debugPath)) + + var pluginsDebugPath = Path.GetFullPath(Path.Combine(appRootPath, @"..\..\Plugins")); + if (Directory.Exists(pluginsDebugPath)) { - _pluginsDebugDir = new DirectoryInfo(debugPath); + _pluginsDebugDir = new DirectoryInfo(pluginsDebugPath); } + + _themeRegistry = EngineContext.Current.Resolve(); } - protected string ResolveDebugFilePath(string virtualPath) + protected internal string ResolveDebugFilePath(string virtualPath) { if (!_isDebug) return null; @@ -45,7 +49,7 @@ protected string ResolveDebugFilePath(string virtualPath) if (!d.TryGetValue(virtualPath, out var debugPath)) { - if (!IsPluginPath(virtualPath, out var appRelativePath)) + if (!IsExtensionPath(virtualPath, out var root, out var appRelativePath)) { // don't query again in this request d[virtualPath] = null; @@ -61,7 +65,7 @@ protected string ResolveDebugFilePath(string virtualPath) { using (_rwLock.GetWriteLock()) { - debugPath = FindDebugFile(appRelativePath); + debugPath = FindDebugFile(appRelativePath, root); _cachedDebugFilePaths[appRelativePath] = d[appRelativePath] = debugPath; } } @@ -72,59 +76,85 @@ protected string ResolveDebugFilePath(string virtualPath) return debugPath; } - private string FindDebugFile(string appRelativePath) + private string FindDebugFile(string appRelativePath, string root) { if (_pluginsDebugDir == null) return null; - - var unrooted = appRelativePath.Substring(10); // strip "~/Plugins/" - string area = unrooted.Substring(0, unrooted.IndexOf('/')); - // get "Views/Something/View.cshtml" - var viewPath = unrooted.Substring(area.Length + 1); + // strip "~/Plugins/" or "~/Themes/" + var unrooted = appRelativePath.Substring(root.Length); - var foldersToCheck = new[] { area, area + "-sym" }; + // either plugin or theme name + var extensionName = unrooted.Substring(0, unrooted.IndexOf('/')); - foreach (var folder in foldersToCheck) + // get "Views/Something/View.cshtml" + var relativePath = unrooted.Substring(extensionName.Length + 1); + + if (root == "~/Themes/") + { + var theme = _themeRegistry.GetThemeManifest(extensionName); + if (theme != null && theme.IsSymbolicLink) + { + // Linked theme folders cannot compute cache dependencies correctly when + // working with source paths. We must determine the link target path, + var finalPath = Path.Combine(theme.Path, relativePath.Replace('/', '\\')); + return File.Exists(finalPath) ? finalPath : null; + } + } + else { - var pluginDir = new DirectoryInfo(Path.Combine(_pluginsDebugDir.FullName, folder)); - if (pluginDir != null && pluginDir.Exists) + // Root is "~/Plugin/" + var foldersToCheck = new[] { extensionName, extensionName + "-sym" }; + + foreach (var folder in foldersToCheck) { - var result = Path.Combine(pluginDir.FullName, viewPath).Replace("/", "\\"); - return File.Exists(result) ? result : null; + var pluginDir = new DirectoryInfo(Path.Combine(_pluginsDebugDir.FullName, folder)); + if (pluginDir != null && pluginDir.Exists) + { + var result = Path.Combine(pluginDir.FullName, relativePath).Replace("/", "\\"); + return File.Exists(result) ? result : null; + } } } return null; } - private bool IsPluginPath(string virtualPath, out string appRelativePath) + private bool IsExtensionPath(string virtualPath, out string root, out string appRelativePath) { + root = null; appRelativePath = virtualPath; + if (virtualPath != null && virtualPath.Length > 0 && virtualPath[0] != '~') { appRelativePath = VirtualPathUtility.ToAppRelative(virtualPath); } - - var result = appRelativePath.StartsWith("~/Plugins/", StringComparison.InvariantCultureIgnoreCase); - return result; + + if (appRelativePath.StartsWith("~/Plugins/", StringComparison.InvariantCultureIgnoreCase)) + { + root = "~/Plugins/"; + return true; + } + + if (appRelativePath.StartsWith("~/Themes/", StringComparison.InvariantCultureIgnoreCase)) + { + root = "~/Themes/"; + return true; + } + + return false; } } - internal class DebugPluginVirtualFile : VirtualFile + internal class DebugVirtualFile : VirtualFile { - private readonly string _debugPath; - - public DebugPluginVirtualFile(string virtualPath, string debugPath) + public DebugVirtualFile(string virtualPath, string debugPath) : base(virtualPath) { - this._debugPath = debugPath; + this.PhysicalPath = debugPath; } - public string PhysicalPath - { - get { return _debugPath; } - } + public string PhysicalPath { get; } public override bool IsDirectory { @@ -133,8 +163,8 @@ public override bool IsDirectory public override Stream Open() { - var fileView = new FileStream(_debugPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - return fileView; + var fileStream = new FileStream(PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return fileStream; } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs index 53483e0ade..9e5474c48e 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHelper.cs @@ -26,9 +26,9 @@ static ThemeHelper() { ThemesBasePath = CommonHelper.GetAppSetting("sm:ThemesBasePath", "~/Themes/").EnsureEndsWith("/"); - var pattern = @"^{0}(.*)/(.+)(\.)(png|gif|jpg|jpeg|css|scss|less|js|cshtml|svg|json|liquid)(\?explicit)*$".FormatInvariant(ThemesBasePath); + var pattern = @"^{0}(.*)/(.+)(\.)(png|gif|jpg|jpeg|css|scss|js|cshtml|svg|json|liquid)(\?explicit)*$".FormatInvariant(ThemesBasePath); s_inheritableThemeFilePattern = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); - s_themeVarsPattern = new Regex(@"\.(db|app)/themevars(.scss|.less)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); + s_themeVarsPattern = new Regex(@"\.(db|app)/themevars(.scss)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); s_moduleImportsPattern = new Regex(@"\.app/moduleimports.scss$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); s_extensionlessPathPattern = new Regex(@"~/(.+)/([^/.]*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); } @@ -37,7 +37,7 @@ internal static IEnumerable RemoveVirtualImports(IEnumerable vir { Guard.NotNull(virtualPathDependencies, nameof(virtualPathDependencies)); - // determine the virtual themevars.(scss|less) import reference + // determine the virtual themevarsscss import reference var themeVarsFile = virtualPathDependencies.Where(x => ThemeHelper.PathIsThemeVars(x)).FirstOrDefault(); var moduleImportsFile = virtualPathDependencies.Where(x => ThemeHelper.PathIsModuleImports(x)).FirstOrDefault(); @@ -131,10 +131,6 @@ internal static IsStyleSheetResult IsStyleSheet(string path) { return new IsStyleSheetResult { Path = path, IsSass = true }; } - else if (extension == ".less") - { - return new IsStyleSheetResult { Path = path, IsLess = true }; - } else if (extension.IsEmpty()) { // StyleBundles are extension-less, so we have to ask 'BundleTable' @@ -196,7 +192,6 @@ internal class IsStyleSheetResult { public string Path { get; set; } public bool IsCss { get; set; } - public bool IsLess { get; set; } public bool IsSass { get; set; } public bool IsBundle { get; set; } @@ -206,8 +201,6 @@ public string Extension { if (IsSass) return ".scss"; - else if (IsLess) - return ".less"; else if (IsBundle) return ""; @@ -217,7 +210,7 @@ public string Extension public bool IsPreprocessor { - get { return IsLess || IsSass; } + get { return IsSass; } } public bool IsThemeVars diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs index 39195b6f37..f70718141f 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeHtmlExtensions.cs @@ -37,7 +37,7 @@ public static MvcHtmlString ThemeVarLabel(this HtmlHelper html, ThemeVariableInf } result.Append("
    "); - result.Append(html.Label(html.NameForThemeVar(info), displayName.NullEmpty() ?? "$" + info.Name, new { @class = "col-form-label" })); + result.Append(html.Label(html.NameForThemeVar(info), displayName.NullEmpty() ?? "$" + info.Name, new { @class = "x-col-form-label" })); if (hint.HasValue()) { result.Append(html.Hint(hint).ToHtmlString()); @@ -107,7 +107,7 @@ public static MvcHtmlString ThemeVarChainInfo(this HtmlHelper html, ThemeVariabl if (currentTheme != info.Manifest) { // the variable is inherited from a base theme: display an info badge - var chainInfo = " {0}".FormatCurrent(info.Manifest.ThemeName); + var chainInfo = "{0}".FormatCurrent(info.Manifest.ThemeName); return MvcHtmlString.Create(chainInfo); } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs index 48f1d0ff63..29cc34de82 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemeVarsRepository.cs @@ -18,7 +18,6 @@ internal class ThemeVarsRepository private static readonly Regex s_valueLessVars = new Regex(@"[@][a-zA-Z0-9_-]+", RegexOptions.Compiled); //private static readonly Regex s_valueWhitelist = new Regex(@"^[#@]?[a-zA-Z0-9""' _\.,-]*$"); - const string LessVarPrefix = "@var_"; const string SassVarPrefix = "$"; public string GetPreprocessorCss(string extension, string themeName, int storeId) @@ -27,9 +26,8 @@ public string GetPreprocessorCss(string extension, string themeName, int storeId Guard.IsPositive(storeId, nameof(storeId)); var variables = GetVariables(themeName, storeId); + var css = Transform(variables); - var isLess = extension.IsCaseInsensitiveEqual(".less"); - var css = Transform(variables, isLess); return css; } @@ -75,8 +73,7 @@ internal virtual ExpandoObject GetRawVariables(string themeName, int storeId) string cacheKey = FrameworkCacheConsumer.BuildThemeVarsCacheKey(themeName, storeId); return HttpRuntime.Cache.GetOrAdd(cacheKey, () => { - var themeVarService = EngineContext.Current.Resolve(); - return themeVarService.GetThemeVariables(themeName, storeId) ?? new ExpandoObject(); + return GetRawVariablesCore(themeName, storeId); }); } } @@ -87,35 +84,17 @@ private ExpandoObject GetRawVariablesCore(string themeName, int storeId) return themeVarService.GetThemeVariables(themeName, storeId) ?? new ExpandoObject(); } - private string Transform(IDictionary parameters, bool toLess) + private string Transform(IDictionary parameters) { if (parameters.Count == 0) return string.Empty; - var prefix = toLess ? LessVarPrefix : SassVarPrefix; + var prefix = SassVarPrefix; var sb = new StringBuilder(); foreach (var parameter in parameters.Where(kvp => kvp.Value.HasValue())) { - var value = parameter.Value; - if (toLess) - { - value = s_valueLessVars.Replace(value, match => - { - // Replaces all occurences of @varname with @var_varname (in case of LESS). - // The LESS compiler would throw exceptions otherwise, because the main variables file - // is not loaded yet at this stage. - var refVar = match.Value; - if (!refVar.StartsWith(prefix)) - { - refVar = "{0}{1}".FormatInvariant(prefix, refVar.Substring(1)); - } - - return refVar; - }); - } - - sb.AppendFormat("{0}{1}: {2};\n", prefix, parameter.Key, value); + sb.AppendFormat("{0}{1}: {2};\n", prefix, parameter.Key, parameter.Value); } return sb.ToString(); diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs b/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs index b61e5a2890..dc4102e768 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/ThemingVirtualPathProvider.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Web; using System.Web.Caching; using System.Web.Hosting; using SmartStore.Core.Infrastructure; using SmartStore.Core.Themes; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.Theming { @@ -45,7 +45,7 @@ public override bool FileExists(string virtualPath) else { // Let system VPP check for this file - virtualPath = result.OriginalVirtualPath; + virtualPath = result.ResultVirtualPath ?? result.OriginalVirtualPath; } } } @@ -55,75 +55,88 @@ public override bool FileExists(string virtualPath) public override VirtualFile GetFile(string virtualPath) { - string debugPath = ResolveDebugFilePath(virtualPath); - if (debugPath != null) - { - return new DebugPluginVirtualFile(virtualPath, debugPath); - } + VirtualFile file = null; + string debugPath = null; var result = GetResolveResult(virtualPath); if (result != null) { + // File is an inherited theme file. Set the result virtual path. + virtualPath = result.ResultVirtualPath ?? result.OriginalVirtualPath; if (!result.IsExplicit) { - return new InheritedVirtualThemeFile(result); + file = new InheritedVirtualThemeFile(result); } - else + } + + if (result == null || file is InheritedVirtualThemeFile) + { + // Handle plugin and symlinked theme folders in debug mode. + debugPath = ResolveDebugFilePath(virtualPath); + if (debugPath != null) { - virtualPath = result.OriginalVirtualPath; + file = new DebugVirtualFile(file?.VirtualPath ?? virtualPath, debugPath); } } - return _previous.GetFile(virtualPath); + return file ?? _previous.GetFile(virtualPath); } - - public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) - { - string debugPath = ResolveDebugFilePath(virtualPath); - if (debugPath != null) + + public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) + { + if (virtualPathDependencies == null) { - return new CacheDependency(debugPath); + return _previous.GetFileHash(virtualPath, virtualPathDependencies); } - return new CacheDependency(MapDependencyPaths(virtualPathDependencies.Cast()), utcStart); + var fileNames = MapDependencyPaths(virtualPathDependencies.Cast()); + var combiner = HashCodeCombiner.Start(); + + foreach (var fileName in fileNames) + { + combiner.Add(new FileInfo(fileName)); + } + + return combiner.CombinedHashString; } - public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies) + public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart) { - string debugPath = ResolveDebugFilePath(virtualPath); - if (debugPath != null) + if (virtualPathDependencies == null) { - return File.GetLastWriteTime(debugPath).ToString(); + return null; } - return _previous.GetFileHash(virtualPath, virtualPathDependencies); + return new CacheDependency(MapDependencyPaths(virtualPathDependencies.Cast()), utcStart); } internal static string[] MapDependencyPaths(IEnumerable virtualPathDependencies) { + // Maps virtual to physical paths. Used to compute cache dependecies and file hashes. + var fileNames = new List(); foreach (var dep in virtualPathDependencies) { - var result = GetResolveResult(dep); - if (result != null) + string mappedPath = null; + var file = HostingEnvironment.VirtualPathProvider.GetFile(dep); + + if (file is InheritedVirtualThemeFile file1) { - fileNames.Add(result.IsExplicit ? HostingEnvironment.MapPath(result.OriginalVirtualPath) : result.ResultPhysicalPath); + mappedPath = file1.ResolveResult.ResultPhysicalPath; } - else + else if (file is DebugVirtualFile file2) { - string mappedPath = null; - if (_isDebug) - { - // We're in debug mode and in dev environment: try to map path with VPP - var file = HostingEnvironment.VirtualPathProvider.GetFile(dep) as DebugPluginVirtualFile; - if (file != null) - { - mappedPath = file.PhysicalPath; - } - } + mappedPath = file2.PhysicalPath; + } + else if (file != null) + { + mappedPath = HostingEnvironment.MapPath(file.VirtualPath); + } - fileNames.Add(mappedPath ?? HostingEnvironment.MapPath(dep)); + if (mappedPath.HasValue()) + { + fileNames.Add(mappedPath); } } @@ -134,8 +147,7 @@ private static InheritedThemeFileResult GetResolveResult(string virtualPath) { var d = _requestState.GetState(); - InheritedThemeFileResult result; - if (!d.TryGetValue(virtualPath, out result)) + if (!d.TryGetValue(virtualPath, out var result)) { result = d[virtualPath] = EngineContext.Current.Resolve().Resolve(virtualPath); } diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs index 682ed4e8ad..b0404c4afe 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs @@ -65,7 +65,12 @@ protected bool IsStoreClosed } } - protected bool HasMessages + public bool EnableHoneypotProtection + { + get { return _helper.EnableHoneypotProtection; } + } + + protected bool HasMessages { get { @@ -162,29 +167,6 @@ public override string Layout } } - /// - /// Return a value indicating whether the working language and theme support RTL (right-to-left) - /// - /// - public bool ShouldUseRtlTheme() - { - var lang = _helper.Services?.WorkContext?.WorkingLanguage; - if (lang == null) - { - return false; - } - - var supportRtl = lang.Rtl; - if (supportRtl) - { - // Ensure that the active theme also supports it - var manifest = this.ThemeManifest; - supportRtl = manifest == null ? supportRtl : manifest.SupportRtl; - } - - return supportRtl; - } - /// /// Gets the manifest of the current active theme /// @@ -275,6 +257,17 @@ public string ModifyUrl(string url, string query, string removeQueryName = null) return url2; } + public string GenerateHelpUrl(HelpTopic topic) + { + var seoCode = WorkContext?.WorkingLanguage?.UniqueSeoCode; + if (seoCode.IsEmpty()) + { + return topic?.EnPath; + } + + return SmartStoreVersion.GenerateHelpUrl(seoCode, topic); + } + public string GenerateHelpUrl(string path) { var seoCode = WorkContext?.WorkingLanguage?.UniqueSeoCode; @@ -285,7 +278,33 @@ public string GenerateHelpUrl(string path) return SmartStoreVersion.GenerateHelpUrl(seoCode, path); } - } + + /// + /// Tries to find a matching localization file for a given culture in the following order + /// (assuming is 'de-DE', is 'lang-*.js' and is 'en-US'): + /// + /// Exact match > lang-de-DE.js + /// Neutral culture > lang-de.js + /// Any region for language > lang-de-CH.js + /// Exact match for fallback culture > lang-en-US.js + /// Neutral fallback culture > lang-en.js + /// Any region for fallback language > lang-en-GB.js + /// + /// + /// The ISO culture code to get a localization file for, e.g. 'de-DE' + /// The virtual path to search in + /// The pattern to match, e.g. 'lang-*.js'. The wildcard char MUST exist. + /// Optional. + /// Result + public LocalizationFileResolveResult ResolveLocalizationFile( + string culture, + string virtualPath, + string pattern, + string fallbackCulture = "en") + { + return _helper.LocalizationFileResolver.Resolve(culture, virtualPath, pattern, true, fallbackCulture); + } + } public abstract class WebViewPage : WebViewPage { diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs index 269478f827..8ac41dfef5 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPageHelper.cs @@ -11,6 +11,7 @@ using SmartStore.Web.Framework.Filters; using SmartStore.Core.Domain; using SmartStore.Services.Customers; +using SmartStore.Core.Domain.Security; namespace SmartStore.Web.Framework.Theming { @@ -19,7 +20,7 @@ public class WebViewPageHelper private bool _initialized; private ControllerContext _controllerContext; private ExpandoObject _themeVars; - private IList _internalNotifications; + private ICollection _internalNotifications; private int? _currentCategoryId; private int? _currentManufacturerId; @@ -28,20 +29,21 @@ public class WebViewPageHelper private bool? _isHomePage; private bool? _isMobileDevice; private bool? _isStoreClosed; + private bool? _enableHoneypot; - public WebViewPageHelper() + public WebViewPageHelper() { T = NullLocalizer.Instance; } public Localizer T { get; set; } + public ILocalizationFileResolver LocalizationFileResolver { get; set; } public ICommonServices Services { get; set; } public IThemeRegistry ThemeRegistry { get; set; } public IThemeContext ThemeContext { get; set; } public IMobileDeviceHelper MobileDeviceHelper { get; set; } - public StoreInformationSettings StoreInfoSettings { get; set; } - public void Initialize(ViewContext viewContext) + public void Initialize(ViewContext viewContext) { if (!_initialized) { @@ -149,26 +151,41 @@ public bool IsStoreClosed { if (!_isStoreClosed.HasValue) { - _isStoreClosed = Services.WorkContext.CurrentCustomer.IsAdmin() && StoreInfoSettings.StoreClosedAllowForAdmins ? false : StoreInfoSettings.StoreClosed; + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); + _isStoreClosed = Services.WorkContext.CurrentCustomer.IsAdmin() && settings.StoreClosedAllowForAdmins ? false : settings.StoreClosed; } return _isStoreClosed.Value; } } - public IEnumerable ResolveNotifications(NotifyType? type) + public bool EnableHoneypotProtection + { + get + { + if (!_enableHoneypot.HasValue) + { + var settings = Services.Settings.LoadSetting(Services.StoreContext.CurrentStore.Id); + _enableHoneypot = settings.EnableHoneypotProtection; + } + + return _enableHoneypot.Value; + } + } + + public IEnumerable ResolveNotifications(NotifyType? type) { IEnumerable result = Enumerable.Empty(); if (_internalNotifications == null) { string key = NotifyAttribute.NotificationsKey; - IList entries; + ICollection entries; var tempData = _controllerContext.Controller.TempData; if (tempData.ContainsKey(key)) { - entries = tempData[key] as IList; + entries = tempData[key] as ICollection; if (entries != null) { result = result.Concat(entries); @@ -178,14 +195,14 @@ public IEnumerable ResolveNotifications(NotifyType? type) var viewData = _controllerContext.Controller.ViewData; if (viewData.ContainsKey(key)) { - entries = viewData[key] as IList; + entries = viewData[key] as ICollection; if (entries != null) { result = result.Concat(entries); } } - _internalNotifications = new List(result); + _internalNotifications = new HashSet(result); } if (type == null) diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs b/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs index b747d09478..be8e5eee0d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Breadcrumb.cs @@ -5,7 +5,7 @@ namespace SmartStore.Web.Framework.UI { public interface IBreadcrumb { - void Track(MenuItem item); + void Track(MenuItem item, bool prepend = false); IReadOnlyList Trail { get; } } @@ -13,7 +13,7 @@ public class DefaultBreadcrumb : IBreadcrumb { private List _trail; - public void Track(MenuItem item) + public void Track(MenuItem item, bool prepend = false) { Guard.NotNull(item, nameof(item)); @@ -22,7 +22,14 @@ public void Track(MenuItem item) _trail = new List(); } - _trail.Add(item); + if (prepend) + { + _trail.Insert(0, item); + } + else + { + _trail.Add(item); + } } public IReadOnlyList Trail diff --git a/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs index 824fbb0b8f..241a3f953e 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/BundleBuilder.cs @@ -54,14 +54,14 @@ public string Build(BundleType type, IEnumerable files) bundleFor = BundleTable.Bundles.GetBundleFor(bundleVirtualPath); if (bundleFor == null) { - var nullOrderer = new NullOrderer(); + var nullOrderer = new NullOrderer(); - Bundle bundle = (type == BundleType.Script) ? + Bundle bundle = (type == BundleType.Script) ? new CustomScriptBundle(bundleVirtualPath) as Bundle : new SmartStyleBundle(bundleVirtualPath) as Bundle; - bundle.Orderer = nullOrderer; + bundle.Orderer = nullOrderer; - bundle.Include(files.ToArray()); + bundle.Include(files.ToArray()); BundleTable.Bundles.Add(bundle); } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs deleted file mode 100644 index 17b06c4570..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; -using System.Text; -using System.Web; -using System.Web.Mvc; -using SmartStore.Core.Logging; -using SmartStore.Services.Localization; -using SmartStore.Utilities; - -namespace SmartStore.Web.Framework.UI.Captcha -{ - public class CaptchaValidatorAttribute : ActionFilterAttribute - { - public CaptchaValidatorAttribute() - { - Logger = NullLogger.Instance; - } - - public Lazy CaptchaSettings { get; set; } - public ILogger Logger { get; set; } - public Lazy LocalizationService { get; set; } - - public override void OnActionExecuting(ActionExecutingContext filterContext) - { - var valid = false; - - try - { - var captchaSettings = CaptchaSettings.Value; - var verifyUrl = CommonHelper.GetAppSetting("g:RecaptchaVerifyUrl"); - var recaptchaResponse = filterContext.HttpContext.Request.Form["g-recaptcha-response"]; - - var url = "{0}?secret={1}&response={2}".FormatInvariant( - verifyUrl, - HttpUtility.UrlEncode(captchaSettings.ReCaptchaPrivateKey), - HttpUtility.UrlEncode(recaptchaResponse) - ); - - using (var client = new WebClient()) - { - var jsonResponse = client.DownloadString(url); - using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonResponse))) - { - var serializer = new DataContractJsonSerializer(typeof(GoogleRecaptchaApiResponse)); - var result = serializer.ReadObject(memoryStream) as GoogleRecaptchaApiResponse; - - if (result == null) - { - Logger.Error(LocalizationService.Value.GetResource("Common.CaptchaUnableToVerify")); - } - else - { - if (result.ErrorCodes == null) - { - valid = result.Success; - } - } - } - } - } - catch (Exception exception) - { - Logger.ErrorsAll(exception); - } - - // this will push the result value into a parameter in our Action - filterContext.ActionParameters["captchaValid"] = valid; - - base.OnActionExecuting(filterContext); - } - } - - - [DataContract] - public class GoogleRecaptchaApiResponse - { - [DataMember(Name = "success")] - public bool Success { get; set; } - - [DataMember(Name = "error-codes")] - public List ErrorCodes { get; set; } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs deleted file mode 100644 index 272b78b9c7..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Infrastructure; -using SmartStore.Utilities; - -namespace SmartStore.Web.Framework.UI.Captcha -{ - public static class HtmlExtensions - { - public static string GenerateCaptcha(this HtmlHelper helper) - { - var sb = new StringBuilder(); - var captchaSettings = EngineContext.Current.Resolve(); - var workContext = EngineContext.Current.Resolve(); - var widgetUrl = CommonHelper.GetAppSetting("g:RecaptchaWidgetUrl"); - var elementId = "GoogleRecaptchaWidget"; - - var url = "{0}?onload=googleRecaptchaOnloadCallback&render=explicit&hl={1}".FormatInvariant( - widgetUrl, - workContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower() - ); - - sb.AppendLine(""); - sb.AppendLine("
    ".FormatInvariant(elementId)); - sb.AppendLine("".FormatInvariant(url)); - - return sb.ToString(); - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs index f4f366f912..10806732cb 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Component.cs @@ -9,7 +9,6 @@ public abstract class Component : IUiComponent protected Component() { this.HtmlAttributes = new RouteValueDictionary(); - this.ComponentVersion = BootstrapVersion.V4; } public string Id @@ -47,11 +46,5 @@ public virtual bool NameIsRequired return false; } } - - public BootstrapVersion ComponentVersion - { - get; - set; - } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs index 4e5ceef345..3746291113 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs @@ -110,12 +110,6 @@ public virtual TBuilder Name(string name) return this as TBuilder; } - public virtual TBuilder ComponentVersion(BootstrapVersion value) - { - this.Component.ComponentVersion = value; - return this as TBuilder; - } - public virtual TBuilder HtmlAttributes(object attributes) { return this.HtmlAttributes(CommonHelper.ObjectToDictionary(attributes)); diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs index f37e81d0f8..75bb64220f 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Enums.cs @@ -4,13 +4,15 @@ namespace SmartStore.Web.Framework.UI { public enum BadgeStyle { - Default, + Secondary, Primary, Success, Info, Warning, - Danger - } + Danger, + Light, + Dark + } public enum ButtonStyle { @@ -24,10 +26,4 @@ public enum ButtonStyle Dark, Link } - - public enum BootstrapVersion - { - V2 = 2, - V4 = 4 - } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuCreatedEvent.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuCreatedEvent.cs new file mode 100644 index 0000000000..d9ba06c2a3 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Menu/MenuCreatedEvent.cs @@ -0,0 +1,22 @@ +using System; +using SmartStore.Collections; + +namespace SmartStore.Web.Framework.UI +{ + public class MenuCreatedEvent + { + public MenuCreatedEvent(string menuName, TreeNode root, string selectedItemToken) + { + Guard.NotEmpty(menuName, nameof(menuName)); + Guard.NotNull(root, nameof(root)); + + MenuName = menuName; + Root = root; + SelectedItemToken = selectedItemToken; + } + + public string MenuName { get; private set; } + public TreeNode Root { get; private set; } + public string SelectedItemToken { get; set; } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs index abccb95769..6a8b2f4d8b 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItem.cs @@ -39,6 +39,8 @@ public NavigationItem() public string Text { get; set; } + public bool Rtl { get; set; } + public string Summary { get; set; } public string BadgeText { get; set; } @@ -91,8 +93,11 @@ public string ActionName } set { - _actionName = value; - _routeName = (string)(_url = null); + if (_actionName != value) + { + _actionName = value; + _routeName = (string)(_url = null); + } } } @@ -105,8 +110,11 @@ public string ControllerName } set { - _controllerName = value; - _routeName = (string)(_url = null); + if (_controllerName != value) + { + _controllerName = value; + _routeName = (string)(_url = null); + } } } @@ -119,8 +127,11 @@ public string RouteName } set { - _routeName = value; - _controllerName = _actionName = (string)(_url = null); + if (_routeName != value) + { + _routeName = value; + _controllerName = _actionName = (string)(_url = null); + } } } @@ -135,10 +146,12 @@ public string Url } set { - _url = value; - _routeName = _controllerName = (string)(_actionName = null); - this.RouteValues.Clear(); - + if (_url != value) + { + _url = value; + _routeName = _controllerName = (string)(_actionName = null); + this.RouteValues.Clear(); + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs index 11bca055dc..1671e86478 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/NavigationItemBuilder.cs @@ -151,7 +151,7 @@ public TBuilder Summary(string value) return (this as TBuilder); } - public TBuilder Badge(string value, BadgeStyle style = BadgeStyle.Default, bool condition = true) + public TBuilder Badge(string value, BadgeStyle style = BadgeStyle.Secondary, bool condition = true) { if (condition) { diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs index 002b320ffc..7e024d559d 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Pager/PagerRenderer.cs @@ -192,7 +192,7 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) if (pager.ShowSummary && pager.Model.TotalPages > 1) { - writer.AddAttribute("class", "pagination-summary pull-left"); + writer.AddAttribute("class", "pagination-summary float-left"); writer.RenderBeginTag("div"); writer.WriteEncodedText(pager.CurrentPageText.FormatInvariant(pager.Model.PageNumber, pager.Model.TotalPages, pager.Model.TotalCount)); writer.RenderEndTag(); // div diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs index 6c72f76581..c55f9d03a0 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/TabStrip/TabStripRenderer.cs @@ -5,11 +5,7 @@ using System.Text; using System.Web.UI; using System.Web.Mvc; -using System.Web.Mvc.Html; -using System.Web.Routing; using SmartStore.Web.Framework.Modelling; -using SmartStore.Core.Infrastructure; -using SmartStore.Web.Framework.Theming; namespace SmartStore.Web.Framework.UI { @@ -297,7 +293,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind string loadedTabName = null; //
  • {text}
  • - item.HtmlAttributes.AppendCssClass("nav-item" + (item.Selected && this.Component.ComponentVersion == BootstrapVersion.V2 ? " active" : "")); // .active for BS2 + item.HtmlAttributes.AppendCssClass("nav-item"); // .active for BS2 if (!item.Selected && !item.Visible) { @@ -306,7 +302,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind if (item.Pull == TabPull.Right) { - item.HtmlAttributes.AppendCssClass("pull-right"); + item.HtmlAttributes.AppendCssClass("float-right"); } writer.AddAttributes(item.HtmlAttributes); @@ -320,7 +316,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind writer.AddAttribute("href", itemId); writer.AddAttribute("data-toggle", "tab"); writer.AddAttribute("data-loaded", "true"); - writer.AddAttribute("class", "nav-link" + (item.Selected && this.Component.ComponentVersion == BootstrapVersion.V4 ? " active" : "")); // .active for BS4 + writer.AddAttribute("class", "nav-link" + (item.Selected ? " active" : "")); loadedTabName = GetTabName(item) ?? itemId; } else @@ -389,7 +385,7 @@ protected virtual string RenderItemLink(HtmlTextWriter writer, Tab item, int ind temp += " badge-" + item.BadgeStyle.ToString().ToLower(); if (base.Component.Position == TabsPosition.Left) { - temp += " pull-right"; // looks nicer + temp += " float-right"; // looks nicer } writer.AddAttribute("class", temp); writer.RenderBeginTag("span"); diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs index 6ae412afbc..c4b82c8352 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/Window/WindowRenderer.cs @@ -44,12 +44,6 @@ protected override void WriteHtmlCore(HtmlTextWriter writer) ? (win.CloseOnBackdropClick ? "true" : "static") : "false"; - if (win.ContentUrl.HasValue()) - { - // TBD: (BS4) does this still work? - win.HtmlAttributes["data-remote"] = win.ContentUrl; - } - writer.AddAttributes(win.HtmlAttributes); writer.RenderBeginTag("div"); // div.modal @@ -114,7 +108,7 @@ protected virtual void RenderHeader(HtmlTextWriter writer) if (win.Title.HasValue()) { - writer.Write("".FormatCurrent(win.Id + "Label", win.Title)); + writer.Write("".FormatCurrent(win.Id + "Label", win.Title)); } if (win.ShowClose) @@ -132,10 +126,14 @@ protected virtual void RenderBody(HtmlTextWriter writer) writer.AddAttribute("class", "modal-body"); writer.RenderBeginTag("div"); - if (win.ContentUrl.IsEmpty() && win.Content != null) - { - win.Content.WriteTo(writer); - } + if (win.Content != null) + { + win.Content.WriteTo(writer); + } + else if (win.ContentUrl.HasValue()) + { + writer.Write(" -
    -
    - -

    - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    -
    -
    - -
    - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png deleted file mode 100644 index 438a639c8e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png deleted file mode 100644 index 34ec67629a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png deleted file mode 100644 index 691f6e0c7c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png deleted file mode 100644 index 30d005f256..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png deleted file mode 100644 index 249dcc510a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png deleted file mode 100644 index d2576bd7ae..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png deleted file mode 100644 index 61b95f8cb7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png deleted file mode 100644 index 2f08324416..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png deleted file mode 100644 index 4fb28bf723..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png deleted file mode 100644 index dd1c0a4f8d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png deleted file mode 100644 index e01eabbb10..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png deleted file mode 100644 index 34106e0c08..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png deleted file mode 100644 index 35a05dd0a4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png deleted file mode 100644 index 5ed205bb95..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png deleted file mode 100644 index 799604d967..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png deleted file mode 100644 index 078057f6f9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png deleted file mode 100644 index 02ba441724..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png deleted file mode 100644 index 45f6c27ef5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png deleted file mode 100644 index 4c30c8ce26..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png deleted file mode 100644 index f65286f422..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png deleted file mode 100644 index 9ac440b4c8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png deleted file mode 100644 index ba72c7f896..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png deleted file mode 100644 index adc7af36c7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png deleted file mode 100644 index 485cde8032..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png deleted file mode 100644 index 5e25354d13..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png deleted file mode 100644 index 0e19a97312..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png deleted file mode 100644 index 37d886aa04..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png deleted file mode 100644 index c50b7519c8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png deleted file mode 100644 index cb57905683..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png deleted file mode 100644 index d6def9e36c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png deleted file mode 100644 index 7a993614b7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png deleted file mode 100644 index 9567f6af54..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png deleted file mode 100644 index 99cb983eca..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png deleted file mode 100644 index 7ac35c9846..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png deleted file mode 100644 index a2c644bddc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png deleted file mode 100644 index 8738d2eb21..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png deleted file mode 100644 index d51df3c293..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png deleted file mode 100644 index 62bbb95aa4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png deleted file mode 100644 index 0199681774..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png deleted file mode 100644 index 6c973fcde8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png deleted file mode 100644 index 009582ced9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png deleted file mode 100644 index c9cec75704..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png deleted file mode 100644 index 648b1d0735..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png deleted file mode 100644 index ccc1eb7f31..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png deleted file mode 100644 index b1aa6c3d10..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png deleted file mode 100644 index d4517e1c16..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png deleted file mode 100644 index ae7cc0620d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png deleted file mode 100644 index 061ff46943..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png deleted file mode 100644 index d86548cd52..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png deleted file mode 100644 index 89b0166a4c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png deleted file mode 100644 index 0cbaadc72b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png deleted file mode 100644 index e8df06db98..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png deleted file mode 100644 index 383aea4fa1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png deleted file mode 100644 index 68e38ab252..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png deleted file mode 100644 index 39be8180d7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png deleted file mode 100644 index 2b05f43030..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png deleted file mode 100644 index bc99e85cf4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png deleted file mode 100644 index d7c86c3c7d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png deleted file mode 100644 index 8a73d4e5aa..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png deleted file mode 100644 index f9d90b924c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png deleted file mode 100644 index c7b0b1f7e9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png deleted file mode 100644 index c268b87dff..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png deleted file mode 100644 index 7b7b83611d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png deleted file mode 100644 index 4d3e482836..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png deleted file mode 100644 index 6a9186516f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png deleted file mode 100644 index bbc5f049c6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp3.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp3.png deleted file mode 100644 index 137afabfff..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp3.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png deleted file mode 100644 index caa154cea3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png deleted file mode 100644 index 81994a291a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png deleted file mode 100644 index 948b643180..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png deleted file mode 100644 index 97a8a3b191..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png deleted file mode 100644 index d70aaa75ba..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png deleted file mode 100644 index a6b55f6cc2..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png deleted file mode 100644 index 04423b4965..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png deleted file mode 100644 index 76230d3060..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png deleted file mode 100644 index 44a2d2c7e8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png deleted file mode 100644 index 0e4b20ae0f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png deleted file mode 100644 index b98ff86015..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png deleted file mode 100644 index 4f5f61f424..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png deleted file mode 100644 index a3568dd4d5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png deleted file mode 100644 index 4a71c01b60..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png deleted file mode 100644 index 24fc0ae534..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png deleted file mode 100644 index 162b0fb9b5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png deleted file mode 100644 index f5e46cff8a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png deleted file mode 100644 index a55ba848a1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png deleted file mode 100644 index 934f18247f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png deleted file mode 100644 index 639e180215..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png deleted file mode 100644 index 362ffdfce1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png deleted file mode 100644 index cae2c95cff..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png deleted file mode 100644 index d9906e2e0d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png deleted file mode 100644 index b62459b768..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png deleted file mode 100644 index 629270d3f1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png deleted file mode 100644 index 4c7a0855e9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png deleted file mode 100644 index a3a1dbcf73..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png deleted file mode 100644 index 3de371311f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png deleted file mode 100644 index b896b27673..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png deleted file mode 100644 index 0f6bbae201..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png deleted file mode 100644 index c7d4da88f7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png deleted file mode 100644 index 75e014ee90..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png deleted file mode 100644 index 6e8003c424..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png deleted file mode 100644 index dda399e3df..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png deleted file mode 100644 index 1e7c12f801..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png deleted file mode 100644 index d066ecbbeb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png deleted file mode 100644 index 2de5bed7d3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png deleted file mode 100644 index a8d7b142d7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png deleted file mode 100644 index e699f0baac..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png deleted file mode 100644 index 98001f5451..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png deleted file mode 100644 index 0e7cbc05cc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png deleted file mode 100644 index 4a394e527d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png deleted file mode 100644 index 4ff58d7e42..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png deleted file mode 100644 index 3b1b54fd45..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png deleted file mode 100644 index 098859c245..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png deleted file mode 100644 index 4065bdfd90..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png deleted file mode 100644 index 81e33ebe16..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png deleted file mode 100644 index 912abbd9be..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png deleted file mode 100644 index 762346076e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png deleted file mode 100644 index 9edd1c0f4f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png deleted file mode 100644 index 6bd4ab7a60..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png deleted file mode 100644 index 9fa593557f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png deleted file mode 100644 index 2ff894edd6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png deleted file mode 100644 index 28f610a6df..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png deleted file mode 100644 index 1edba7688f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png deleted file mode 100644 index 4c5411efb8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png deleted file mode 100644 index 42aa0026f2..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png deleted file mode 100644 index ce04201323..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png deleted file mode 100644 index 74aef831b7..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png deleted file mode 100644 index 9b79766cc3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png deleted file mode 100644 index e9045442ff..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png deleted file mode 100644 index e52bc641f1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png deleted file mode 100644 index 277b23d0e5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png deleted file mode 100644 index 3d7b07c515..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png deleted file mode 100644 index 758a0e1c1a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png deleted file mode 100644 index 204c9d0c8b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png deleted file mode 100644 index 49111a90be..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png deleted file mode 100644 index d8d6a81c16..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png deleted file mode 100644 index dfd46f9ce9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png deleted file mode 100644 index 4752a952c4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png deleted file mode 100644 index 27e4c2358b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png deleted file mode 100644 index 9dd4c903fb..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png deleted file mode 100644 index e6f3174fb0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png deleted file mode 100644 index 919089b353..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png deleted file mode 100644 index 09bbde7eaf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png deleted file mode 100644 index 81f80e9a41..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png deleted file mode 100644 index 043623c4d8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png deleted file mode 100644 index efa8206090..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png deleted file mode 100644 index f391025d7e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png deleted file mode 100644 index e1fe0bee23..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png deleted file mode 100644 index bcd6f0e15c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png deleted file mode 100644 index a78b68e37b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png deleted file mode 100644 index 541c14efc0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png deleted file mode 100644 index 812e3c012b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png deleted file mode 100644 index f1e060e539..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png deleted file mode 100644 index f77a21c38f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png deleted file mode 100644 index a69dff9948..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png deleted file mode 100644 index 6ec08d7d49..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png deleted file mode 100644 index 8306dbbfcf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png deleted file mode 100644 index ae294cfaa0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png deleted file mode 100644 index 9518f75d47..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png deleted file mode 100644 index f0888c7393..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png deleted file mode 100644 index a7d89042a3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png deleted file mode 100644 index cf0f2cfed9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png deleted file mode 100644 index 403ecad8be..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png deleted file mode 100644 index a74b16d60b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png deleted file mode 100644 index 07887a064b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png deleted file mode 100644 index 75075c7819..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png deleted file mode 100644 index 704a347e04..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp3.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp3.png deleted file mode 100644 index d2624de030..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp3.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png deleted file mode 100644 index ecc3d28342..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png deleted file mode 100644 index 6518619a22..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png deleted file mode 100644 index d01440fe28..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png deleted file mode 100644 index 3f53d37854..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png deleted file mode 100644 index f8c4fc8268..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png deleted file mode 100644 index 874915f710..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png deleted file mode 100644 index ef52e6a4d8..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png deleted file mode 100644 index 812e3c012b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png deleted file mode 100644 index 3964c49235..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png deleted file mode 100644 index 9bd9e03df4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png deleted file mode 100644 index d3f6ec562b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png deleted file mode 100644 index 5da647e5d0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png deleted file mode 100644 index 8250def1cf..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png deleted file mode 100644 index 806b8ba37a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png deleted file mode 100644 index 5e4d56b863..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png deleted file mode 100644 index 7e20ff067f..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png deleted file mode 100644 index 577f6efd0d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png deleted file mode 100644 index 18a73cd5c0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png deleted file mode 100644 index 6a94dd89cd..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png deleted file mode 100644 index ca0983bbd9..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png deleted file mode 100644 index 9c533a0505..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png deleted file mode 100644 index a7efed7e31..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png deleted file mode 100644 index 03f87f879a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png deleted file mode 100644 index a85638623e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png deleted file mode 100644 index 98206fc2e3..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png deleted file mode 100644 index 3c3bb4c44e..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png deleted file mode 100644 index 7d056d0241..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png deleted file mode 100644 index 5650971b09..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png deleted file mode 100644 index 5253aab3d0..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png deleted file mode 100644 index b3acbb1c8c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png deleted file mode 100644 index a284f79764..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png deleted file mode 100644 index 80c165b033..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png deleted file mode 100644 index 09de7ab1d4..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png deleted file mode 100644 index 51a0bbb6ef..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png deleted file mode 100644 index e3bed85703..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png deleted file mode 100644 index 5380d08d00..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png deleted file mode 100644 index 5a5dde849b..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png deleted file mode 100644 index 6897534d98..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png deleted file mode 100644 index 63f5d343b5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png deleted file mode 100644 index 4017f86712..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png deleted file mode 100644 index 69154a0218..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png deleted file mode 100644 index a5cb228dde..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png deleted file mode 100644 index dea5e195ad..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png deleted file mode 100644 index f0756cd29c..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png deleted file mode 100644 index 3ea3d5d6b5..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png deleted file mode 100644 index 1d6f4f13fc..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png deleted file mode 100644 index 537184e705..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png deleted file mode 100644 index ae496495d1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png deleted file mode 100644 index e307dec1ca..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png deleted file mode 100644 index 468189e950..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png deleted file mode 100644 index ef29588dc6..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/remove-upload - Copy.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/remove-upload - Copy.png deleted file mode 100644 index a93bdf82c1..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/remove-upload - Copy.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png deleted file mode 100644 index 34ec67629a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png deleted file mode 100644 index b81b18de12..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png deleted file mode 100644 index dc951d454a..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png deleted file mode 100644 index 8a08b5224d..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png deleted file mode 100644 index 4a0c64e663..0000000000 Binary files a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png and /dev/null differ diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html deleted file mode 100644 index 101c734e29..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - -Roxy file manager - - - - - - - - - - - - - - - - - - -
    -
    - - - -
    -
    - Loading directories...
    - -
    -
    -
      -
      -
      - - -
      - - - - - - -
      - Order by: -    - -    - -
      -
      -
      -
      - Loading files...
      - -
      -
      - This folder is empty -
      -
      - No files found -
      -
        -
        -
        -
        -    © 2013 - RoxyFileman - -
        Status bar
        -
        - - - -
        -
        - -

        - -
        -
        -
        -
        -
        -
        -
        - - -
        -
        - -
        -
        -
        - -
        - - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js index 366bf704dd..ff67f93a71 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js @@ -19,27 +19,51 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function FileSelected(file){ - /** - * file is an object containing following properties: - * - * fullPath - path to the file - absolute from your site root - * path - directory in which the file is located - absolute from your site root - * size - size of the file in bytes - * time - timestamo of last modification - * name - file name - * ext - file extension - * width - if the file is image, this will be the width of the original image, 0 otherwise - * height - if the file is image, this will be the height of the original image, 0 otherwise - * - */ - alert('"' + file.fullPath + "\" selected.\n To integrate with CKEditor or TinyMCE change INTEGRATION setting in conf.json. For more details see the Installation instructions at http://www.roxyfileman.com/install."); -} -function GetSelectedValue(){ - /** - * This function is called to retrieve selected value when custom integration is used. - * Url parameter selected will override this value. - */ - - return ""; +function FileSelected(file) { + /** + * file is an object containing following properties: + * + * fullPath - path to the file - absolute from your site root + * path - directory in which the file is located - absolute from your site root + * size - size of the file in bytes + * time - timestamp of last modification + * name - file name + * ext - file extension + * width - if the file is image, this will be the width of the original image, 0 otherwise + * height - if the file is image, this will be the height of the original image, 0 otherwise + * + */ + + var p = (window.opener || window.parent); + + // Set the value of field sent to Fileman via URL param "field". + var fieldId = RoxyUtils.GetUrlParam('field'); + //opener.document.getElementById(fieldId).value = file.fullPath; + p.window.jQuery('#' + fieldId).val(file.fullPath).trigger('change').trigger('input'); + + //// Set the source of an image which id is sent to Fileman via URL param "img". + // opener.document.getElementById(RoxyUtils.GetUrlParam('img')).src = file.fullPath; + + // Close file manager if it's opened in separate window. + if (window.opener) { + self.close(); + } + else { + // We put the modal dialog's ID in "mid" + p.window.closePopup(RoxyUtils.GetUrlParam('mid')); + } } + +function GetSelectedValue() { + /** + * This function is called to retrieve selected value when custom integration is used. + * Url parameter selected will override this value. + */ + + var p = (window.opener || window.parent); + var fieldId = RoxyUtils.GetUrlParam('field'); + + if (fieldId) { + return p.window.jQuery('#' + fieldId).val(); + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js index 46c29faf62..0dd0b780c3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js @@ -19,520 +19,590 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function Directory(fullPath, numDirs, numFiles){ - if(!fullPath) fullPath = ''; - this.fullPath = fullPath; - this.name = RoxyUtils.GetFilename(fullPath); - if(!this.name) - this.name = 'My files'; - this.path = RoxyUtils.GetPath(fullPath); - this.dirs = (numDirs?numDirs:0); - this.files = (numFiles?numFiles:0); - this.filesList = new Array(); - this.Show = function(){ - var html = this.GetHtml(); - var el = null; - el = $('li[data-path="'+this.path+'"]'); - if(el.length == 0) - el = $('#pnlDirList'); - else{ - if(el.children('ul').length == 0) - el.append('
          '); - el = el.children('ul'); - } - if(el){ - el.append(html); - this.SetEvents(); - } - }; - this.SetEvents = function(){ - var el = this.GetElement(); - if(RoxyFilemanConf.MOVEDIR){ - el.draggable({helper:makeDragDir,start:startDragDir,cursorAt: { left: 10 ,top:10},delay:200}); - } - el = el.children('div'); - el.click(function(e){ - selectDir(this); - }); +$(function () { + $('#pnlDirList').on('contextmenu', '.dir-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + closeMenus('file'); + selectDir(this, true); + var t = e.pageY; + var menuEnd = t + $('#menuDir').height() + 30; + if (menuEnd > $(window).height()) { + offset = menuEnd - $(window).height() + 30; + t -= offset; + } + if (t < 0) + t = 0; + $('#menuDir').css({ + top: t + 'px', + left: e.pageX + 'px' + }).show(); - el.bind('contextmenu', function(e) { - e.stopPropagation(); - e.preventDefault(); - closeMenus('file'); - selectDir(this); - var t = e.pageY - $('#menuDir').height(); - if(t < 0) - t = 0; - $('#menuDir').css({ - top: t+'px', - left: e.pageX+'px' - }).show(); + return false; + }); - return false; - }); + $('#pnlDirList').on('click', '.dir-item', function (e) { + e.preventDefault(); + selectDir(this); + }); - el.droppable({drop:moveObject,over:dragFileOver,out:dragFileOut}); - el = el.children('.dirPlus'); - el.click(function(e){ - e.stopPropagation(); - var d = Directory.Parse($(this).closest('li').attr('data-path')); - d.Expand(); - }); - }; - this.GetHtml = function(){ - var html = '
        • '; - html += '
          '; - html += ''+this.name+' ('+this.files+')
          '; - html += '
        • '; + $('#pnlDirList').on('click', '.dirPlus', function (e) { + e.stopPropagation(); + e.preventDefault(); + var d = Directory.Parse($(this).closest('li').attr('data-path')); + d.Expand(); + }); +}); - return html; - }; - this.SetStatusBar = function(){ - $('#pnlStatus').html(this.files+' '+(this.files == 1?t('file'):t('files'))); - }; - this.SetSelectedFile = function(path){ - if(path){ - var f = File.Parse(path); - if(f){ - selectFile(f.GetElement()); - } - } - }; - this.Select = function(selectedFile){ - var el = this.GetElement(); - el.children('div').addClass('selected'); - $('#pnlDirList li[data-path!="'+this.fullPath+'"] > div').removeClass('selected'); - el.children('img.dir').prop('src', 'images/folder.png'); - this.SetStatusBar(); - var p = this.GetParent(); - while(p){ - p.Expand(true); - p = p.GetParent(); - } - this.Expand(true); - this.ListFiles(true, selectedFile); - setLastDir(this.fullPath); - }; - this.GetElement = function(){ - return $('li[data-path="'+this.fullPath+'"]'); - }; - this.IsExpanded = function(){ - var el = this.GetElement().children('ul'); - return (el && el.is(":visible")); - }; - this.IsListed = function(){ - if($('#hdDir').val() == this.fullPath) - return true; - return false; - }; - this.GetExpanded = function(el){ - var ret = new Array(); - if(!el) - el = $('#pnlDirList'); - el.children('li').each(function(){ - var path = $(this).attr('data-path'); - var d = new Directory(path); - if(d){ - if(d.IsExpanded() && path) - ret.push(path); - ret = ret.concat(d.GetExpanded(d.GetElement().children('ul'))); - } - }); +function Directory(fullPath, numDirs, numFiles) { + if (!fullPath) fullPath = ''; + this.fullPath = fullPath; + this.name = RoxyUtils.GetFilename(fullPath); + if (!this.name) + this.name = 'My files'; + this.path = RoxyUtils.GetPath(fullPath); + this.dirs = (numDirs ? numDirs : 0); + this.files = (numFiles ? numFiles : 0); + this.filesList = []; - return ret; - }; - this.RestoreExpanded = function(expandedDirs){ - for(i = 0; i < expandedDirs.length; i++){ - var d = Directory.Parse(expandedDirs[i]); - if(d) - d.Expand(true); - } - }; - this.GetParent = function(){ - return Directory.Parse(this.path); - }; - this.SetOpened = function(){ - var li = this.GetElement(); - if(li.find('li').length < 1) - li.children('div').children('.dirPlus').prop('src', 'images/blank.gif'); - else if(this.IsExpanded()) - li.children('div').children('.dirPlus').prop('src', 'images/dir-minus.png'); - else - li.children('div').children('.dirPlus').prop('src', 'images/dir-plus.png'); - }; - this.Update = function(newPath){ - var el = this.GetElement(); - if(newPath){ - this.fullPath = newPath; - this.name = RoxyUtils.GetFilename(newPath); - if(!this.name) - this.name = 'My files'; - this.path = RoxyUtils.GetPath(newPath); - } - el.attr('data-path', this.fullPath); - el.attr('data-dirs', this.dirs); - el.attr('data-files', this.files); - el.children('div').children('.name').html(this.name+' ('+this.files+')'); - this.SetOpened(); - }; - this.LoadAll = function(selectedDir){ - var expanded = this.GetExpanded(); - var dirListURL = RoxyFilemanConf.DIRLIST; - if(!dirListURL){ - alert(t('E_ActionDisabled')); - return; - } - $('#pnlLoadingDirs').show(); - $('#pnlDirList').hide(); - dirListURL = RoxyUtils.AddParam(dirListURL, 'type', RoxyUtils.GetUrlParam('type')); + this.Show = function () { + var html = this.GetHtml(); + var el = null; + el = $('li[data-path="' + this.path + '"]'); + if (el.length == 0) + el = $('#pnlDirList'); + else { + if (el.children('ul').length == 0) + el.append('
            '); + el = el.children('ul'); + } + if (el) { + el.append(html); + this.SetEvents(); + } + }; + this.SetEvents = function () { + var el = this.GetElement(); + el.draggable({ + helper: makeDragDir, + start: startDragDir, + cursorAt: { + left: 10, + top: 10 + }, + delay: 200 + }); + el.find('> .dir-item').droppable({ + drop: moveObject, + over: dragFileOver, + out: dragFileOut + }); + }; + this.GetHtml = function () { + var dirClass = (this.dirs > 0 ? "" : " invisible"); - var dir = this; - $.ajax({ - url: dirListURL, - type:'POST', - dataType: 'json', - async: false, - cache: false, - success: function(dirs){ - $('#pnlDirList').children('li').remove(); - for(i = 0; i < dirs.length; i++){ - var d = new Directory(dirs[i].p, dirs[i].d, dirs[i].f); - d.Show(); - } - $('#pnlLoadingDirs').hide(); - $('#pnlDirList').show(); - dir.RestoreExpanded(expanded); - var d = Directory.Parse(selectedDir); - if(d) - d.Select(); - }, - error: function(data){ - $('#pnlLoadingDirs').hide(); - $('#pnlDirList').show(); - alert(t('E_LoadingAjax')+' '+RoxyFilemanConf.DIRLIST); - } - }); - }; - this.Expand = function(show){ - var li = this.GetElement(); - var el = li.children('ul'); - if(this.IsExpanded() && !show) - el.hide(); - else - el.show(); + var html = '
          • '; + html += '
            '; + html += '' + this.name + (parseInt(this.files) ? ' (' + this.files + ')' : '') + '
            '; + html += '
          • '; + + return html; + }; + this.SetStatusBar = function () { + $('#pnlStatus').html(this.files + ' ' + (this.files == 1 ? t('file') : t('files'))); + }; + this.SetSelectedFile = function (path) { + if (path) { + var f = File.Parse(path); + if (f) { + selectFile(f.GetElement()); + } + } + }; + this.Select = function (selectedFile, indeterm) { + var li = this.GetElement(); + var dir = li.find('> .dir-item'); + var currentSelected = getSelectedDir(); - this.SetOpened(); - }; - this.Create = function(newName){ - if(!newName) - return false; - else if(!RoxyFilemanConf.CREATEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.CREATEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {d: this.fullPath, n: newName}, - dataType: 'json', - async:false, - cache: false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - item.LoadAll(RoxyUtils.MakePath(item.fullPath, newName)); - ret = true; - } - else{ - alert(data.msg); - } - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Delete = function(){ - if(!RoxyFilemanConf.DELETEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.DELETEDIR, 'd', this.fullPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {d: this.fullPath}, - dataType: 'json', - async:false, - cache: false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var parent = item.GetParent(); - parent.dirs--; - parent.Update(); - parent.Select(); - item.GetElement().remove(); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Rename = function(newName){ - if(!newName) - return false; - else if(!RoxyFilemanConf.RENAMEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.RENAMEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {d: this.fullPath, n: newName}, - dataType: 'json', - async:false, - cache: false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var newPath = RoxyUtils.MakePath(item.path, newName); - item.Update(newPath); - item.Select(); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.Copy = function(newPath){ - if(!RoxyFilemanConf.COPYDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.COPYDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {d: this.fullPath, n: newPath}, - dataType: 'json', - async:false, - cache: false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var d = Directory.Parse(newPath); - if(d){ - d.LoadAll(d.fullPath); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Move = function(newPath){ - if(!newPath) - return false; - else if(!RoxyFilemanConf.MOVEDIR){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.MOVEDIR, 'd', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {d: this.fullPath, n: newPath}, - dataType: 'json', - async:false, - cache: false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - item.LoadAll(RoxyUtils.MakePath(newPath, item.name)); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+item.name); - } - }); - return ret; - }; - this.ListFiles = function(refresh, selectedFile){ - $('#pnlLoading').show(); - $('#pnlEmptyDir').hide(); - $('#pnlFileList').hide(); - $('#pnlSearchNoFiles').hide(); - this.LoadFiles(refresh, selectedFile); - }; - this.FilesLoaded = function(filesList, selectedFile){ - filesList = this.SortFiles(filesList); - $('#pnlFileList').html(''); - for(i = 0; i < filesList.length; i++){ - var f = filesList[i]; - f.Show(); - } - $('#hdDir').val(this.fullPath); - $('#pnlLoading').hide(); - if($('#pnlFileList').children('li').length == 0) - $('#pnlEmptyDir').show(); - this.files = $('#pnlFileList').children('li').length; - this.Update(); - this.SetStatusBar(); - filterFiles(); - switchView(); - $('#pnlFileList').show(); - this.SetSelectedFile(selectedFile); - }; - this.LoadFiles = function(refresh, selectedFile){ - if(!RoxyFilemanConf.FILESLIST){ - alert(t('E_ActionDisabled')); - return; - } - var ret = new Array(); - var fileURL = RoxyFilemanConf.FILESLIST; - fileURL = RoxyUtils.AddParam(fileURL, 'd', this.fullPath); - fileURL = RoxyUtils.AddParam(fileURL, 'type', RoxyUtils.GetUrlParam('type')); - var item = this; - if(!this.IsListed() || refresh){ + if (indeterm && currentSelected) { + if (currentSelected.fullPath != li.data('path')) { + $('#pnlDirList').data('indeterm', dir); + $('#pnlDirList .indeterm').removeClass('indeterm'); + dir.addClass('indeterm'); + } + } + else { + dir.addClass('selected'); + $('#pnlDirList li[data-path!="' + this.fullPath + '"] > .dir-item').removeClass('selected'); + this.SetStatusBar(); - $.ajax({ - url: fileURL, - type: 'POST', - data: {d: this.fullPath, type: RoxyUtils.GetUrlParam('type')}, - dataType: 'json', - async:true, - cache: false, - success: function(files){ - for(i = 0; i < files.length; i++){ - ret.push(new File(files[i].p, files[i].s, files[i].t, files[i].w, files[i].h)); - } - item.FilesLoaded(ret, selectedFile); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+fileURL); - } - }); - } - else{ - $('#pnlFileList li').each(function(){ - ret.push(new File($(this).attr('data-path'), $(this).attr('data-size'), $(this).attr('data-time'), $(this).attr('data-w'), $(this).attr('data-h'))); - }); - item.FilesLoaded(ret, selectedFile); - } + var p = this.GetParent(); + while (p) { + p.Expand(true); + p = p.GetParent(); + } + this.ListFiles(true, selectedFile); + setLastDir(this.fullPath); + } + }; + this.GetElement = function () { + return $('li[data-path="' + this.fullPath + '"]'); + }; + this.IsExpanded = function () { + var el = this.GetElement().children('ul'); + return (el && el.is(":visible")); + }; + this.IsIndeterm = function () { + var el = this.GetElement().find('> .dir-item'); + return el.is(".indeterm"); + }; + this.IsListed = function () { + if ($('#hdDir').val() == this.fullPath) + return true; + return false; + }; + this.GetExpanded = function (el) { + var ret = new Array(); + if (!el) + el = $('#pnlDirList'); + el.children('li').each(function () { + var path = $(this).attr('data-path'); + var d = new Directory(path); + if (d) { + if (d.IsExpanded() && path) + ret.push(path); + ret = ret.concat(d.GetExpanded(d.GetElement().children('ul'))); + } + }); - return ret; - }; + return ret; + }; + this.RestoreExpanded = function (expandedDirs) { + for (i = 0; i < expandedDirs.length; i++) { + var d = Directory.Parse(expandedDirs[i]); + if (d) + d.Expand(true); + } + }; + this.GetParent = function () { + return Directory.Parse(this.path); + }; + this.SetOpened = function () { + var li = this.GetElement(); + var chevrons = li.children('div').children('.dirPlus'); + if (li.find('li').length < 1) + chevrons.addClass('invisible'); + else if (this.IsExpanded()) + chevrons.removeClass('invisible fa-chevron-right').addClass("fa-chevron-down"); + else + chevrons.removeClass('invisible fa-chevron-down').addClass("fa-chevron-right"); + }; + this.Update = function (newPath) { + var el = this.GetElement(); + if (newPath) { + this.fullPath = newPath; + this.name = RoxyUtils.GetFilename(newPath); + if (!this.name) + this.name = 'My files'; + this.path = RoxyUtils.GetPath(newPath); + } + el.data('path', this.fullPath); + el.data('dirs', this.dirs); + el.data('files', this.files); + el.children('div').children('.name').html(this.name + ' (' + this.files + ')'); + this.SetOpened(); + }; + this.LoadAll = function (selectedDir) { + var expanded = this.GetExpanded(); + var dirListURL = RoxyUtils.GetRootPath(RoxyFilemanConf.DIRLIST); + if (!dirListURL) { + alert(t('E_ActionDisabled')); + return; + } + $('#pnlLoadingDirs').show(); + $('#pnlDirList').hide(); + dirListURL = RoxyUtils.AddParam(dirListURL, 'type', RoxyUtils.GetUrlParam('type')); - this.SortByName = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = a.name.toLowerCase(); - b = b.name.toLowerCase(); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + var dir = this; + $.ajax({ + url: dirListURL, + type: 'POST', + dataType: 'json', + async: false, + cache: false, + success: function (dirs) { + $('#pnlDirList').children('li').remove(); + var d; + for (i = 0; i < dirs.length; i++) { + d = new Directory(dirs[i].p, dirs[i].d, dirs[i].f); + d.Show(); + } + $('#pnlLoadingDirs').hide(); + $('#pnlDirList').show(); + dir.RestoreExpanded(expanded); + d = Directory.Parse(selectedDir); + if (d) d.Select(); + }, + error: function (data) { + $('#pnlLoadingDirs').hide(); + $('#pnlDirList').show(); + alert(t('E_LoadingAjax') + ' ' + RoxyFilemanConf.DIRLIST); + } + }); + }; + this.Expand = function (show) { + var li = this.GetElement(); + var el = li.children('ul'); + if (this.IsExpanded() && !show) + el.hide(); + else + el.show(); - return files; - }; - this.SortBySize = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = parseInt(a.size); - b = parseInt(b.size); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + this.SetOpened(); + }; + this.Create = function (newName) { + if (!newName) + return false; + else if (!RoxyFilemanConf.CREATEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.CREATEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + item.LoadAll(RoxyUtils.MakePath(item.fullPath, newName)); + ret = true; + } else { + alert(data.msg); + } + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Delete = function () { + if (!RoxyFilemanConf.DELETEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.DELETEDIR), 'd', this.fullPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var parent = item.GetParent(); + parent.dirs--; + parent.Update(); + parent.Select(); + item.GetElement().remove(); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Rename = function (newName) { + if (!newName) + return false; + else if (!RoxyFilemanConf.RENAMEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.RENAMEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var newPath = RoxyUtils.MakePath(item.path, newName); + item.Update(newPath); + item.Select(); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.Copy = function (newPath) { + if (!RoxyFilemanConf.COPYDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.COPYDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var d = Directory.Parse(newPath); + if (d) { + d.LoadAll(d.fullPath); + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Move = function (newPath) { + if (!newPath) + return false; + else if (!RoxyFilemanConf.MOVEDIR) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.MOVEDIR), 'd', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + d: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + cache: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + item.LoadAll(RoxyUtils.MakePath(newPath, item.name)); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + item.name); + } + }); + return ret; + }; + this.ListFiles = function (refresh, selectedFile) { + $('#pnlLoading').show(); + $('#pnlEmptyDir').hide(); + $('#pnlFileList').hide(); + $('#pnlSearchNoFiles').hide(); + this.LoadFiles(refresh, selectedFile); + }; + this.FilesLoaded = function (filesList, selectedFile) { + var list = $('#pnlFileList'); + filesList = this.SortFiles(filesList); + + var html = []; + for (i = 0; i < filesList.length; i++) { + var f = filesList[i]; + html.push(f.GenerateHtml()); + } - return files; - }; - this.SortByTime = function(files, order){ - files.sort(function(a, b){ - var x = (order == 'desc'?0:2) - a = parseInt(a.time); - b = parseInt(b.time); - if(a > b) - return -1 + x; - else if(a < b) - return 1 - x; - else - return 0; - }); + // Set Html + list.html(html.join("")); - return files; - }; - this.SortFiles = function(files){ - var order = $('#ddlOrder').val(); - if(!order) - order = 'name'; + // Bind events + list.find('.file-item').tooltip({ + show: { + delay: 700, + duration: 100 + }, + hide: 200, + track: true, + content: tooltipContent + }); - switch(order){ - case 'size': - files = this.SortBySize(files, 'asc'); - break; - case 'size_desc': - files = this.SortBySize(files, 'desc'); - break; - case 'time': - files = this.SortByTime(files, 'asc'); - break; - case 'time_desc': - files = this.SortByTime(files, 'desc'); - break; - case 'name_desc': - files = this.SortByName(files, 'desc'); - break; - default: - files = this.SortByName(files, 'asc'); - } + $('#hdDir').val(this.fullPath); + $('#pnlLoading').hide(); + var liLen = list.children('li').length; + if (liLen == 0) + $('#pnlEmptyDir').show(); + this.files = liLen; + this.Update(); + this.SetStatusBar(); + filterFiles(); + switchView(); + list.show(); + this.SetSelectedFile(selectedFile); + }; + this.LoadFiles = function (refresh, selectedFile) { + if (!RoxyFilemanConf.FILESLIST) { + alert(t('E_ActionDisabled')); + return; + } + var ret = new Array(); + var fileURL = RoxyUtils.GetRootPath(RoxyFilemanConf.FILESLIST); + fileURL = RoxyUtils.AddParam(fileURL, 'd', this.fullPath); + fileURL = RoxyUtils.AddParam(fileURL, 'type', RoxyUtils.GetUrlParam('type')); + var item = this; + if (!this.IsListed() || refresh) { + $.ajax({ + url: fileURL, + type: 'POST', + data: { + d: this.fullPath, + type: RoxyUtils.GetUrlParam('type') + }, + dataType: 'json', + async: true, + cache: false, + success: function (files) { + for (i = 0; i < files.length; i++) { + var f = files[i]; + ret.push(new File(f.p, f.s, f.t, f.w, f.h, f.m)); + } + item.FilesLoaded(ret, selectedFile); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + fileURL); + } + }); + } else { + $('#pnlFileList li').each(function () { + ret.push(new File($(this).attr('data-path'), $(this).attr('data-size'), $(this).attr('data-time'), $(this).attr('data-w'), $(this).attr('data-h'))); + }); + item.FilesLoaded(ret, selectedFile); + } - return files; - }; + return ret; + }; + + this.SortByName = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = a.name.toLowerCase(); + b = b.name.toLowerCase(); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortBySize = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = parseInt(a.size); + b = parseInt(b.size); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortByTime = function (files, order) { + files.sort(function (a, b) { + var x = (order == 'desc' ? 0 : 2) + a = parseInt(a.time); + b = parseInt(b.time); + if (a > b) + return -1 + x; + else if (a < b) + return 1 - x; + else + return 0; + }); + + return files; + }; + this.SortFiles = function (files) { + var order = $('#ddlOrder').val(); + if (!order) + order = 'name'; + + switch (order) { + case 'size': + files = this.SortBySize(files, 'asc'); + break; + case 'size_desc': + files = this.SortBySize(files, 'desc'); + break; + case 'time': + files = this.SortByTime(files, 'asc'); + break; + case 'time_desc': + files = this.SortByTime(files, 'desc'); + break; + case 'name_desc': + files = this.SortByName(files, 'desc'); + break; + default: + files = this.SortByName(files, 'asc'); + } + + return files; + }; } -Directory.Parse = function(path){ - var ret = false; - var li = $('#pnlDirList').find('li[data-path="'+path+'"]'); - if(li.length > 0) - ret = new Directory(li.attr('data-path'), li.attr('data-dirs'), li.attr('data-files')); +Directory.Parse = function (path) { + var ret = false; + var li = $('#pnlDirList').find('li[data-path="' + path + '"]'); + if (li.length > 0) + ret = new Directory(li.attr('data-path'), li.attr('data-dirs'), li.attr('data-files')); - return ret; -}; + return ret; +}; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js index 337faca3bb..9d27380ad6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js @@ -19,212 +19,266 @@ Contact: Lyubomir Arsov, liubo (at) web-lobby.com */ -function File(filePath, fileSize, modTime, w, h){ - this.fullPath = filePath; - this.type = RoxyUtils.GetFileType(filePath); - this.name = RoxyUtils.GetFilename(filePath); - this.ext = RoxyUtils.GetFileExt(filePath); - this.path = RoxyUtils.GetPath(filePath); - this.icon = RoxyUtils.GetFileIcon(filePath); - this.bigIcon = this.icon.replace('filetypes', 'filetypes/big'); - this.image = filePath; - this.size = (fileSize?fileSize:RoxyUtils.GetFileSize(filePath)); - this.time = modTime; - this.width = (w? w: 0); - this.height = (h? h: 0); - this.Show = function(){ - html = '
          • '; - html += ''; - html += ''+RoxyUtils.FormatDate(new Date(this.time * 1000))+''; - html += ''+this.name+''; - html += ''+RoxyUtils.FormatFileSize(this.size)+''; - html += '
          • '; - $('#pnlFileList').append(html); - var li = $("#pnlFileList li:last"); - if(RoxyFilemanConf.MOVEFILE){ - li.draggable({helper:makeDragFile,start:startDragFile,cursorAt: { left: 10 ,top:10},delay:200}); - } - li.click(function(e){ - selectFile(this); - }); - li.dblclick(function(e){ - selectFile(this); - setFile(); - }); - li.tooltip({show:{delay:700},track: true, content:tooltipContent}); - - li.bind('contextmenu', function(e) { - e.stopPropagation(); - e.preventDefault(); - closeMenus('dir'); - selectFile(this); - $(this).tooltip('close'); - var t = e.pageY - $('#menuFile').height(); - if(t < 0) - t = 0; - $('#menuFile').css({ - top: t+'px', - left: e.pageX+'px' - }).show(); - - return false; - }); - }; - this.GetElement = function(){ - return $('li[data-path="'+this.fullPath+'"]'); - }; - this.IsImage = function(){ - var ret = false; - if(this.type == 'image') - ret = true; - return ret; - }; - this.Delete = function(){ - if(!RoxyFilemanConf.DELETEFILE){ - alert(t('E_ActionDisabled')); - return; - } - var deleteUrl = RoxyUtils.AddParam(RoxyFilemanConf.DELETEFILE, 'f', this.fullPath); - var item = this; - $.ajax({ - url: deleteUrl, - type: 'POST', - data: {f: this.fullPath}, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - $('li[data-path="'+item.fullPath+'"]').remove(); - var d = Directory.Parse(item.path); - if(d){ - d.files--; - d.Update(); - d.SetStatusBar(); - } - } - else{ - alert(data.msg); - } - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+deleteUrl); - } - }); - }; - this.Rename = function(newName){ - if(!RoxyFilemanConf.RENAMEFILE){ - alert(t('E_ActionDisabled')); - return false; - } - if(!newName) - return false; - var url = RoxyUtils.AddParam(RoxyFilemanConf.RENAMEFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newName); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {f: this.fullPath, n: newName}, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var newPath = RoxyUtils.MakePath(this.path, newName); - $('li[data-path="'+item.fullPath+'"] .icon').attr('src', RoxyUtils.GetFileIcon(newName)); - $('li[data-path="'+item.fullPath+'"] .name').text(newName); - $('li[data-path="'+newPath+'"]').attr('data-path', newPath); - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Copy = function(newPath){ - if(!RoxyFilemanConf.COPYFILE){ - alert(t('E_ActionDisabled')); - return; - } - var url = RoxyUtils.AddParam(RoxyFilemanConf.COPYFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {f: this.fullPath, n: newPath}, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - var d = Directory.Parse(newPath); - if(d){ - d.files++; - d.Update(); - d.SetStatusBar(); - d.ListFiles(true); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; - this.Move = function(newPath){ - if(!RoxyFilemanConf.MOVEFILE){ - alert(t('E_ActionDisabled')); - return; - } - newFullPath = RoxyUtils.MakePath(newPath, this.name); - var url = RoxyUtils.AddParam(RoxyFilemanConf.MOVEFILE, 'f', this.fullPath); - url = RoxyUtils.AddParam(url, 'n', newFullPath); - var item = this; - var ret = false; - $.ajax({ - url: url, - type: 'POST', - data: {f: this.fullPath, n: newFullPath}, - dataType: 'json', - async:false, - success: function(data){ - if(data.res.toLowerCase() == 'ok'){ - $('li[data-path="'+item.fullPath+'"]').remove(); - var d = Directory.Parse(item.path); - if(d){ - d.files--; - d.Update(); - d.SetStatusBar(); - d = Directory.Parse(newPath); - d.files++; - d.Update(); - } - ret = true; - } - if(data.msg) - alert(data.msg); - }, - error: function(data){ - alert(t('E_LoadingAjax')+' '+url); - } - }); - return ret; - }; + +$(function () { + $(document).on('dblclick', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + selectFile(this); + setFile(); + }); + + $('#pnlFileList').on('contextmenu', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + closeMenus('dir'); + selectFile(this); + $(this).tooltip('close'); + var t = e.pageY; + var menuEnd = t + $('#menuFile').height() + 30; + if (menuEnd > $(window).height()) { + offset = menuEnd - $(window).height() + 30; + t -= offset; + } + $('#menuFile').css({ + top: t + 'px', + left: e.pageX + 'px' + }).show(); + + return false; + }); + + $(document).on('click', '.file-item', function (e) { + e.stopPropagation(); + e.preventDefault(); + + selectFile(this); + }); + + $(document).on('mouseenter', '.file-item', function (e) { + var li = $(this); + + // create draggable + if (!li.data('ui-draggable')) { + li.draggable({ + helper: makeDragFile, + start: startDragFile, + addClasses: false, + appendTo: 'body', + cursorAt: { + left: 10, + top: 10 + }, + delay: 200 + }); + } + }); +}); + +function File(filePath, fileSize, modTime, w, h, mime) { + this.fullPath = filePath; + this.mime = mime; + this.type = RoxyUtils.GetFileType(filePath, mime); + this.icon = RoxyIconHints[this.type]; + this.name = RoxyUtils.GetFilename(filePath); + this.ext = RoxyUtils.GetFileExt(filePath); + this.path = RoxyUtils.GetPath(filePath); + this.image = filePath; + this.size = (fileSize ? fileSize : RoxyUtils.GetFileSize(filePath)); + this.time = modTime; + this.width = (w ? w : 0); + this.height = (h ? h : 0); + this.thumb = this.type === 'image' ? filePath : RoxyUtils.GetAssetPath("images/blank.gif"); + this.GenerateHtml = function () { + var attrs = [ + 'data-mime="' + this.mime + '"', + 'data-path="' + this.fullPath + '"', + 'data-time="' + this.time + '"', + 'data-w="' + this.width + '"', + 'data-h="' + this.height + '"', + 'data-size="' + this.size + '"', + 'title="' + this.name + '"' + ]; + var html = [ + '
          • ', + '
            ', + '' + RoxyUtils.FormatDate(new Date(this.time * 1000)) + '', + '' + this.name + '', + '' + RoxyUtils.FormatFileSize(this.size) + '', + '
          • ' + ].join(""); + + return html; + }; + this.GetElement = function () { + return $('li[data-path="' + this.fullPath + '"]'); + }; + this.IsImage = function () { + return this.type === 'image'; + }; + this.Delete = function () { + if (!RoxyFilemanConf.DELETEFILE) { + alert(t('E_ActionDisabled')); + return; + } + var deleteUrl = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.DELETEFILE), 'f', this.fullPath); + var item = this; + $.ajax({ + url: deleteUrl, + type: 'POST', + data: { + f: this.fullPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + $('li[data-path="' + item.fullPath + '"]').remove(); + var d = Directory.Parse(item.path); + if (d) { + d.files--; + d.Update(); + d.SetStatusBar(); + } + } else { + alert(data.msg); + } + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + deleteUrl); + } + }); + }; + this.Rename = function (newName) { + if (!RoxyFilemanConf.RENAMEFILE) { + alert(t('E_ActionDisabled')); + return false; + } + if (!newName) + return false; + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.RENAMEFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newName); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newName + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var newPath = RoxyUtils.MakePath(this.path, newName); + var fileType = RoxyUtils.GetFileIcon(newName); + var icon = RoxyIconHints[fileType]; + var li = $('li[data-path="' + item.fullPath + '"]'); + li.toggleClass('file-image', fileType == 'image'); + $('.file-icon', li).attr('class', "").addClass('file-icon fa fa-fw fa-' + icon.name).css('color', icon.color); + $('.name', li).text(newName); + $('li[data-path="' + newPath + '"]').attr('data-path', newPath); + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Copy = function (newPath) { + if (!RoxyFilemanConf.COPYFILE) { + alert(t('E_ActionDisabled')); + return; + } + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.COPYFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + var d = Directory.Parse(newPath); + if (d) { + d.files++; + d.Update(); + d.SetStatusBar(); + if (!$("#pnlDirList").data("indeterm")) { + d.ListFiles(true); + } + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; + this.Move = function (newPath) { + if (!RoxyFilemanConf.MOVEFILE) { + alert(t('E_ActionDisabled')); + return; + } + newFullPath = RoxyUtils.MakePath(newPath, this.name); + var url = RoxyUtils.AddParam(RoxyUtils.GetRootPath(RoxyFilemanConf.MOVEFILE), 'f', this.fullPath); + url = RoxyUtils.AddParam(url, 'n', newFullPath); + var item = this; + var ret = false; + $.ajax({ + url: url, + type: 'POST', + data: { + f: this.fullPath, + n: newFullPath + }, + dataType: 'json', + async: false, + success: function (data) { + if (data.res.toLowerCase() == 'ok') { + $('li[data-path="' + item.fullPath + '"]').remove(); + var d = Directory.Parse(item.path); + if (d) { + d.files--; + d.Update(); + d.SetStatusBar(); + d = Directory.Parse(newPath); + d.files++; + d.Update(); + } + ret = true; + } + if (data.msg) + alert(data.msg); + }, + error: function (data) { + alert(t('E_LoadingAjax') + ' ' + url); + } + }); + return ret; + }; } -File.Parse = function(path){ - var ret = false; - var li = $('#pnlFileList').find('li[data-path="'+path+'"]'); - if(li.length > 0) - ret = new File(li.attr('data-path'), li.attr('data-size'), li.attr('data-time'), li.attr('data-w'), li.attr('data-h')); - return ret; +File.Parse = function (path) { + var ret = false; + var li = $('#pnlFileList').find('li[data-path="' + path + '"]'); + if (li.length > 0) + ret = new File(li.data('path'), li.data('size'), li.data('time'), li.data('w'), li.data('h')); + + return ret; }; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js index 209d66f01a..48d5a1092b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js @@ -118,4 +118,4 @@ fileTypeIcons['wmv'] = 'file_extension_wmv.png'; fileTypeIcons['wps'] = 'file_extension_wps.png'; fileTypeIcons['xls'] = 'file_extension_xls.png'; fileTypeIcons['xpi'] = 'file_extension_xpi.png'; -fileTypeIcons['zip'] = 'file_extension_zip.png'; +fileTypeIcons['zip'] = 'file_extension_zip.png'; \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js deleted file mode 100644 index da4170647d..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license -//@ sourceMappingURL=jquery-1.10.2.min.map -*/ -(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="
            ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
            a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
            t
            ",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
            ",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t -}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
            ","
            "],area:[1,"",""],param:[1,"",""],thead:[1,"","
            "],tr:[2,"","
            "],col:[2,"","
            "],td:[3,"","
            "],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
            ","
            "]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); -u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x(" +
            +
            + +
            +
            + +
            +
            +
            +
            +
            + +
            + + +
            +
            + +
            +
            +
            + +
            + + @Html.SmartScripts(this.Url, ResourceLocation.Foot) + @Scripts.Render("~/bundles/roxyfm") + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, "~/Administration/Content/filemanager/lang/", "*.js", null) + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml index c5994e8872..7b4b687a79 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -1,9 +1,8 @@ -@model ScheduleTaskModel -@using SmartStore +@using SmartStore @using SmartStore.Core +@model ScheduleTaskModel @{ ViewBag.Title = T("Admin.System.ScheduleTasks.EditTask") + " - " + Model.Name; - var cronHelpLink = GenerateHelpUrl("Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron"); var returnUrl = ((string)ViewBag.ReturnUrl).NullEmpty(); } @@ -20,17 +19,36 @@ (@T("Common.Back"))
            - - - @if (!Model.IsRunning) + + + @if (!Model.LastHistoryEntry.IsRunning) { -  @T("Admin.System.ScheduleTasks.RunNow") + + + @T("Admin.System.ScheduleTasks.RunNow") + }
            @Html.ValidationSummary(false) - + + @Html.SmartStore().TabStrip().Name("schedule-task-edit").Style(TabsStyle.Material).Items(x => + { + x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Common.History").Text).Content(TabHistory()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "schedule-task-edit", this.Html, this.Model)); + }) +} + +@helper TabGeneral() +{
            + + + + - @if (Model.Duration.HasValue()) + @if (Model.LastHistoryEntry.Duration.HasValue()) { } - @if (Model.LastError.HasValue()) + @if (Model.LastHistoryEntry.Error.HasValue()) { - if (Model.LastSuccess.HasValue && Model.LastSuccess != Model.LastEnd) + if (Model.LastHistoryEntry.SucceededOn.HasValue && Model.LastHistoryEntry.SucceededOn != Model.LastHistoryEntry.FinishedOn) { } @@ -108,7 +150,9 @@ @Html.SmartLabelFor(model => model.NextRun) } @@ -124,7 +168,7 @@ @@ -136,6 +180,75 @@
            @@ -41,6 +59,16 @@ @Html.ValidationMessageFor(model => model.Name)
            + @Html.SmartLabelFor(model => model.RunPerMachine) + +
            + @T(Model.RunPerMachine ? "Common.Yes" : "Common.No") +
            +
            @Html.SmartLabelFor(model => model.Enabled) @@ -61,42 +89,56 @@
            - @Html.SmartLabelFor(model => model.LastStart) + @Html.SmartLabelFor(model => model.LastHistoryEntry.StartedOn) - - @(Model.LastStart.HasValue ? Model.LastStart.Value.ToString("g") : T("Common.Never").Text) +
            + @if (Model.LastHistoryEntry.Id != 0) + { + @Html.DisplayFor(model => model.LastHistoryEntry.StartedOn) + } + else + { + @T("Common.Never") + } +
            - @Html.SmartLabelFor(model => model.Duration) + @Html.SmartLabelFor(model => model.LastHistoryEntry.Duration) - @Html.DisplayFor(model => model.Duration) +
            + @Html.DisplayFor(model => model.LastHistoryEntry.Duration) +
            - @Html.SmartLabelFor(model => model.LastError) + @Html.SmartLabelFor(model => model.LastHistoryEntry.Error) - @Html.DisplayFor(model => model.LastError) +
            + @Html.DisplayFor(model => model.LastHistoryEntry.Error) +
            - @Html.SmartLabelFor(model => model.LastSuccess) + @Html.SmartLabelFor(model => model.LastHistoryEntry.SucceededOn) - @Model.LastSuccess.Value.ToString("g") +
            + @Html.DisplayFor(model => model.LastHistoryEntry.SucceededOn) +
            - @Model.NextRun.Value.ToString("g") +
            + @Html.DisplayFor(model => model.NextRun) +
              -
            @T("Admin.System.ScheduleTasks.CronHelp", cronHelpLink)
            +
            @T("Admin.System.ScheduleTasks.CronHelp", GenerateHelpUrl(HelpTopic.CronExpressions))
            } +@helper TabHistory() +{ +
            + @Html.Raw((string)ViewBag.HistoryCleanupNote) + +
            + + +
            + @(Html.Telerik().Grid() + .Name("schedule-task-history-grid") + .DataKeys(keys => + { + keys.Add(x => x.Id).RouteKey("id"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("HistoryList", "ScheduleTask", new { taskId = Model.Id }) + .Delete("DeleteHistoryEntry", "ScheduleTask"); + }) + .ClientEvents(e => + { + e.OnRowDataBound("OnTaskHistoryGridRowDataBound"); + }) + .Columns(columns => + { + columns.Bound(x => x.StartedOn) + .Title(T("Common.ExecutedOn")) + .Width(260) + .ClientTemplate( + "
            " + + "
            <#= StartedOnString #>
            " + + "
            <#= StartedOnPretty #>
            " + + "
            "); + columns.Bound(x => x.FinishedOn) + .Title(T("Common.FinishedOn")) + .Width(260) + .ClientTemplate( + "
            " + + "
            <#= FinishedOnString #>
            " + + "
            <#= FinishedOnPretty #>
            " + + "
            "); + columns.Bound(x => x.Duration) + .Width(260); + columns.Bound(x => x.Succeeded) + .ClientTemplate( + "<# if(Succeeded){ #>" + @Html.SymbolForBool("Succeeded") + "" + T("Common.Succeeded") + "<# } #>" + + "<# if(!Succeeded){ #>
            " + T("Common.Error") + ": <#= Error #>
            <# } #>"); + columns.Bound(x => x.MachineName); + columns.Command(commands => + { + commands.Delete().Localize(T); + }) + .Width(200) + .HtmlAttributes(new { align = "right" }); + }) + .Pageable(settings => settings.PageSize((int)ViewBag.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true)) +
            +} + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml index 5a7806bd66..dfe34c40b8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/AllSettings.cshtml @@ -25,14 +25,17 @@ .Columns(columns => { columns.Bound(x => x.Name).Width("25%"); - columns.Bound(x => x.Value).Width("55%").EditorTemplateName("MultilineText"); + columns.Bound(x => x.Value) + .Width("55%") + .HtmlAttributes(new { style = "max-width: 600px;" }) + .EditorTemplateName("MultilineText"); columns.Bound(x => x.Store).EditorTemplateName("Store").Width("15%"); columns.Command(commands => { commands.Edit().Localize(T); commands.Delete().Localize(T); - }); - + }) + .HtmlAttributes(new { align = "right" }); }) .ToolBar(x => x.Insert()) .Editable(x => @@ -63,7 +66,11 @@ function grid_onStoreEdit(e) { if (e.mode == "edit") { _.delay(function () { - $('#Store').val(e.dataItem['StoreId']).trigger('change'); + $('#Store') + .data('select-selected-id', e.dataItem['StoreId']) + .data('select-init-text', e.dataItem['Store']) + .val(e.dataItem['StoreId']) + .trigger('change'); }, 0); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml index 727b184698..3621f776d2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Blog.cshtml @@ -22,16 +22,19 @@ @Html.ValidationSummary(false) - - - - - +
            +
            +
            + @Html.SmartLabelFor(model => model.Enabled) +
            +
            + @Html.SettingEditorFor(model => model.Enabled, Html.CheckBoxFor(model => model.Enabled, new { data_toggler_for = "#pnlBlogEnabled" })) + @Html.ValidationMessageFor(model => model.Enabled) +
            +
            +
            + +
            - @Html.SmartLabelFor(model => model.Enabled) - - @Html.SettingEditorFor(model => model.Enabled) - @Html.ValidationMessageFor(model => model.Enabled) -
            @@ -156,8 +155,7 @@ @Html.SmartLabelFor(model => model.DeliveryTimeIdForEmptyStock) @@ -180,6 +178,24 @@ + + + + + + + + @@ -252,7 +268,7 @@ @Html.SmartLabelFor(model => model.EnableHtmlTextCollapser) @@ -261,7 +277,7 @@ @Html.SmartLabelFor(model => model.HtmlTextCollapsedHeight) @@ -328,7 +344,7 @@ @Html.SmartLabelFor(model => model.ShowCategoryProductNumber) @@ -355,8 +371,7 @@ @Html.SmartLabelFor(model => model.SubCategoryDisplayType) @@ -384,8 +399,7 @@ @Html.SmartLabelFor(model => model.DefaultViewMode) @@ -403,8 +417,7 @@ @Html.SmartLabelFor(model => model.DefaultSortOrder) @@ -440,8 +453,7 @@ @Html.SmartLabelFor(model => model.PriceDisplayType) @@ -532,7 +544,7 @@ @Html.SmartLabelFor(model => model.LabelAsNewForMaxDays) @@ -620,7 +632,7 @@ @Html.SmartLabelFor(model => model.EmailAFriendEnabled) @@ -655,7 +667,7 @@ @Html.SmartLabelFor(model => model.RecentlyViewedProductsEnabled) @@ -673,7 +685,7 @@ @Html.SmartLabelFor(model => model.RecentlyAddedProductsEnabled) @@ -691,7 +703,7 @@ @Html.SmartLabelFor(model => model.ProductsAlsoPurchasedEnabled) @@ -718,7 +730,7 @@ @Html.SmartLabelFor(model => model.ShowManufacturerInProductDetail) @@ -790,7 +802,7 @@ @Html.SmartLabelFor(model => model.ShowShareButton) @@ -814,7 +826,7 @@ @Html.SmartLabelFor(model => model.ShowManufacturersOnHomepage) @@ -832,7 +844,7 @@ @Html.SmartLabelFor(model => model.ShowManufacturersInOffCanvas) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index f9a8905d6d..65a1bf4f4a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -1,5 +1,4 @@ @model CustomerUserSettingsModel -@using Telerik.Web.Mvc.UI; @using SmartStore.Core.Domain.Customers; @{ ViewBag.Title = T("Admin.Configuration.Settings.CustomerUser").Text; @@ -30,7 +29,7 @@ x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.AddressFormFields").Text).Content(TabAddressFormFields().ToHtmlString()); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.DateTimeSettings").Text).Content(TabDateTimeSettings()); x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.ExternalAuthenticationSettings").Text).Content(TabExternalAuthenticationSettings()); - + x.Add().Text(T("Admin.Configuration.Settings.CustomerUser.Privacy").Text).Content(TabPrivacy()); EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "customersettings-edit", this.Html, this.Model)); }) } @@ -50,7 +49,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.UsernamesEnabled) @@ -79,8 +78,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormat) @@ -99,8 +97,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberMethod) @@ -109,8 +106,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberVisibility) @@ -120,8 +116,7 @@ @Html.SmartLabelFor(model => model.CustomerSettings.UserRegistrationType) @@ -139,8 +134,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.RegisterCustomerRoleId) @@ -150,7 +145,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) @@ -235,24 +231,6 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage) - - - - - - - -
            @Html.SmartLabelFor(model => model.PostsPageSize) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index 6914d3ea0f..efcb02b519 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -1,5 +1,4 @@ @model CatalogSettingsModel -@using Telerik.Web.Mvc.UI; @{ ViewBag.Title = T("Admin.Configuration.Settings.Catalog").Text; } @@ -46,7 +45,7 @@ @Html.SmartLabelFor(model => model.ShowBestsellersOnHomepage) - @Html.CheckBoxFor(model => model.ShowBestsellersOnHomepage, new { data_toggler_for = "#pnlNumberOfBestsellersOnHomepage" }) + @Html.SettingEditorFor(model => model.ShowBestsellersOnHomepage, Html.CheckBoxFor(model => model.ShowBestsellersOnHomepage, new { data_toggler_for = "#pnlNumberOfBestsellersOnHomepage" })) @Html.ValidationMessageFor(model => model.ShowBestsellersOnHomepage)
            - @Html.SettingOverrideCheckbox(model => model.DeliveryTimeIdForEmptyStock) - @Html.DropDownListFor(model => model.DeliveryTimeIdForEmptyStock, Model.AvailableDeliveryTimes, T("Common.Unspecified")) + @Html.SettingEditorFor(model => model.DeliveryTimeIdForEmptyStock, @Html.DropDownListFor(model => model.DeliveryTimeIdForEmptyStock, Model.AvailableDeliveryTimes, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.DeliveryTimeIdForEmptyStock)
            + @Html.SmartLabelFor(model => model.DisplayTextForZeroPrices) + + @Html.SettingEditorFor(model => model.DisplayTextForZeroPrices) + @Html.ValidationMessageFor(model => model.DisplayTextForZeroPrices) +
            + @Html.SmartLabelFor(model => model.PriceDisplayStyle) + + @Html.EnumSettingEditorFor(model => model.PriceDisplayStyle) + @Html.ValidationMessageFor(model => model.PriceDisplayStyle) +
            @Html.SmartLabelFor(model => model.IgnoreDiscounts) @@ -213,7 +229,7 @@ @Html.SmartLabelFor(model => model.CompareProductsEnabled) - @Html.CheckBoxFor(model => model.CompareProductsEnabled, new { data_toggler_for = "#pnlCompareProducts" }) + @Html.SettingEditorFor(model => model.CompareProductsEnabled, Html.CheckBoxFor(model => model.CompareProductsEnabled, new { data_toggler_for = "#pnlCompareProducts" })) @Html.ValidationMessageFor(model => model.CompareProductsEnabled)
            - @Html.CheckBoxFor(model => model.EnableHtmlTextCollapser, new { data_toggler_for = "#pnlHtmlTextCollapsedHeight" }) + @Html.SettingEditorFor(model => model.EnableHtmlTextCollapser, Html.CheckBoxFor(model => model.EnableHtmlTextCollapser, new { data_toggler_for = "#pnlHtmlTextCollapsedHeight" })) @Html.ValidationMessageFor(model => model.EnableHtmlTextCollapser)
            - @Html.SettingEditorFor(model => model.HtmlTextCollapsedHeight) + @Html.SettingEditorFor(model => model.HtmlTextCollapsedHeight, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.HtmlTextCollapsedHeight)
            - @Html.CheckBoxFor(model => model.ShowCategoryProductNumber, new { data_toggler_for = "#pnlShowCategoryProductNumberIncludingSubcategories" }) + @Html.SettingEditorFor(model => model.ShowCategoryProductNumber, Html.CheckBoxFor(model => model.ShowCategoryProductNumber, new { data_toggler_for = "#pnlShowCategoryProductNumberIncludingSubcategories" })) @Html.ValidationMessageFor(model => model.ShowCategoryProductNumber)
            - @Html.SettingOverrideCheckbox(model => Model.SubCategoryDisplayType) - @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes) + @Html.SettingEditorFor(model => model.SubCategoryDisplayType, @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes)) @Html.ValidationMessageFor(model => model.SubCategoryDisplayType)
            - @Html.SettingOverrideCheckbox(model => Model.DefaultViewMode) - @Html.DropDownListFor(model => model.DefaultViewMode, Model.AvailableDefaultViewModes) + @Html.SettingEditorFor(model => model.DefaultViewMode, @Html.DropDownListFor(model => model.DefaultViewMode, Model.AvailableDefaultViewModes)) @Html.ValidationMessageFor(model => model.DefaultViewMode)
            - @Html.SettingOverrideCheckbox(model => Model.DefaultSortOrder) - @Html.DropDownListFor(model => model.DefaultSortOrder, Model.AvailableSortOrderModes) + @Html.SettingEditorFor(model => model.DefaultSortOrder, @Html.DropDownListFor(model => model.DefaultSortOrder, Model.AvailableSortOrderModes)) @Html.ValidationMessageFor(model => model.DefaultSortOrder)
            - @Html.SettingOverrideCheckbox(model => Model.PriceDisplayType) - @Html.DropDownListFor(model => model.PriceDisplayType, Model.AvailablePriceDisplayTypes) + @Html.SettingEditorFor(model => model.PriceDisplayType, @Html.DropDownListFor(model => model.PriceDisplayType, Model.AvailablePriceDisplayTypes)) @Html.ValidationMessageFor(model => model.PriceDisplayType)
            - @Html.SettingEditorFor(model => model.LabelAsNewForMaxDays) + @Html.SettingEditorFor(model => model.LabelAsNewForMaxDays, null, new { postfix = T("Time.Days").Text }) @Html.ValidationMessageFor(model => model.LabelAsNewForMaxDays)
            - @Html.CheckBoxFor(model => model.EmailAFriendEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailAFriend" }) + @Html.SettingEditorFor(model => model.EmailAFriendEnabled, Html.CheckBoxFor(model => model.EmailAFriendEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailAFriend" })) @Html.ValidationMessageFor(model => model.EmailAFriendEnabled)
            - @Html.CheckBoxFor(model => model.RecentlyViewedProductsEnabled, new { data_toggler_for = "#pnlRecentlyViewedProductsNumber" }) + @Html.SettingEditorFor(model => model.RecentlyViewedProductsEnabled, Html.CheckBoxFor(model => model.RecentlyViewedProductsEnabled, new { data_toggler_for = "#pnlRecentlyViewedProductsNumber" })) @Html.ValidationMessageFor(model => model.RecentlyViewedProductsEnabled)
            - @Html.CheckBoxFor(model => model.RecentlyAddedProductsEnabled, new { data_toggler_for = "#pnlRecentlyAddedProductsNumber" }) + @Html.SettingEditorFor(model => model.RecentlyAddedProductsEnabled, Html.CheckBoxFor(model => model.RecentlyAddedProductsEnabled, new { data_toggler_for = "#pnlRecentlyAddedProductsNumber" })) @Html.ValidationMessageFor(model => model.RecentlyAddedProductsEnabled)
            - @Html.CheckBoxFor(model => model.ProductsAlsoPurchasedEnabled, new { data_toggler_for = "#pnlProductsAlsoPurchasedNumber" }) + @Html.SettingEditorFor(model => model.ProductsAlsoPurchasedEnabled, Html.CheckBoxFor(model => model.ProductsAlsoPurchasedEnabled, new { data_toggler_for = "#pnlProductsAlsoPurchasedNumber" })) @Html.ValidationMessageFor(model => model.ProductsAlsoPurchasedEnabled)
            - @Html.CheckBoxFor(model => model.ShowManufacturerInProductDetail, new { data_toggler_for = "#pnlManufacturerPictures" }) + @Html.SettingEditorFor(model => model.ShowManufacturerInProductDetail, Html.CheckBoxFor(model => model.ShowManufacturerInProductDetail, new { data_toggler_for = "#pnlManufacturerPictures" })) @Html.ValidationMessageFor(model => model.ShowManufacturerInProductDetail)
            - @Html.CheckBoxFor(model => model.ShowShareButton, new { data_toggler_for = "#pnlPageShareCode" }) + @Html.SettingEditorFor(model => model.ShowShareButton, Html.CheckBoxFor(model => model.ShowShareButton, new { data_toggler_for = "#pnlPageShareCode" })) @Html.ValidationMessageFor(model => model.ShowShareButton)
            - @Html.CheckBoxFor(model => model.ShowManufacturersOnHomepage, new { data_toggler_for = "#pnlManufacturerItemsToDisplayOnHomepage" }) + @Html.SettingEditorFor(model => model.ShowManufacturersOnHomepage, Html.CheckBoxFor(model => model.ShowManufacturersOnHomepage, new { data_toggler_for = "#pnlManufacturerItemsToDisplayOnHomepage" })) @Html.ValidationMessageFor(model => model.ShowManufacturersOnHomepage)
            - @Html.CheckBoxFor(model => model.ShowManufacturersInOffCanvas, new { data_toggler_for = "#pnlManufacturerItemsToDisplayInOffCanvas" }) + @Html.SettingEditorFor(model => model.ShowManufacturersInOffCanvas, Html.CheckBoxFor(model => model.ShowManufacturersInOffCanvas, new { data_toggler_for = "#pnlManufacturerItemsToDisplayInOffCanvas" })) @Html.ValidationMessageFor(model => model.ShowManufacturersInOffCanvas)
            - @Html.CheckBoxFor(model => model.CustomerSettings.UsernamesEnabled, new { data_toggler_for = "#pnlUsernamesEnabled" }) + @Html.SettingEditorFor(model => model.CustomerSettings.UsernamesEnabled, Html.CheckBoxFor(model => model.CustomerSettings.UsernamesEnabled, new { data_toggler_for = "#pnlUsernamesEnabled" })) @Html.ValidationMessageFor(model => model.CustomerSettings.UsernamesEnabled)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNameFormat, ((CustomerNameFormat)Model.CustomerSettings.CustomerNameFormat).ToSelectList()) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNameFormat) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberMethod) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberMethod, Model.CustomerSettings.AvailableCustomerNumberMethods) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNumberMethod) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberMethod)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberVisibility) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberVisibility, Model.CustomerSettings.AvailableCustomerNumberVisibilities) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.CustomerNumberVisibility) @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberVisibility)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.UserRegistrationType, ((UserRegistrationType)Model.CustomerSettings.UserRegistrationType).ToSelectList()) + @Html.EnumSettingEditorFor(model => model.CustomerSettings.UserRegistrationType) @Html.ValidationMessageFor(model => model.CustomerSettings.UserRegistrationType)
            - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.RegisterCustomerRoleId) - @Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified")) + @Html.SettingEditorFor(model => model.CustomerSettings.RegisterCustomerRoleId, + Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.CustomerSettings.RegisterCustomerRoleId)
            - @Html.CheckBoxFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, new { data_toggler_for = "#pnlDefaultAvatarEnabled" }) + @Html.SettingEditorFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, + Html.CheckBoxFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars, new { data_toggler_for = "#pnlDefaultAvatarEnabled" })) @Html.ValidationMessageFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars)
            - @Html.SmartLabelFor(model => model.CustomerSettings.StoreLastIpAddress) - - @Html.SettingEditorFor(model => model.CustomerSettings.StoreLastIpAddress) - @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastIpAddress) -
            - @Html.SmartLabelFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) - - @Html.SettingEditorFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) - @Html.ValidationMessageFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) -
            } @@ -282,6 +260,24 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.TitleEnabled) + + + @Html.SmartLabelFor(model => model.CustomerSettings.FirstNameRequired) + + + @Html.SettingEditorFor(model => model.CustomerSettings.FirstNameRequired) + @Html.ValidationMessageFor(model => model.CustomerSettings.FirstNameRequired) + + + + + @Html.SmartLabelFor(model => model.CustomerSettings.LastNameRequired) + + + @Html.SettingEditorFor(model => model.CustomerSettings.LastNameRequired) + @Html.ValidationMessageFor(model => model.CustomerSettings.LastNameRequired) + + @Html.SmartLabelFor(model => model.CustomerSettings.DateOfBirthEnabled) @@ -296,7 +292,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CompanyEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.CompanyEnabled, new { data_toggler_for = "#pnlCompanyRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.CompanyEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CompanyEnabled, new { data_toggler_for = "#pnlCompanyRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CompanyEnabled) @@ -314,7 +311,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.StreetAddressEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlStreetAddressRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddressEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlStreetAddressRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.StreetAddressEnabled) @@ -332,7 +330,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.StreetAddress2Enabled) - @Html.CheckBoxFor(model => model.CustomerSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlStreetAddress2Required" }) + @Html.SettingEditorFor(model => model.CustomerSettings.StreetAddress2Enabled, + Html.CheckBoxFor(model => model.CustomerSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlStreetAddress2Required" })) @Html.ValidationMessageFor(model => model.CustomerSettings.StreetAddress2Enabled) @@ -350,7 +349,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.ZipPostalCodeEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlZipPostalCodeRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.ZipPostalCodeEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlZipPostalCodeRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.ZipPostalCodeEnabled) @@ -368,7 +368,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CityEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.CityEnabled, new { data_toggler_for = "#pnlCityRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.CityEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CityEnabled, new { data_toggler_for = "#pnlCityRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CityEnabled) @@ -386,7 +387,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.CountryEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.CountryEnabled, new { data_toggler_for = "#pnlStateProvincEnabled" }) + @Html.SettingEditorFor(model => model.CustomerSettings.CountryEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.CountryEnabled, new { data_toggler_for = "#pnlStateProvincEnabled" })) @Html.ValidationMessageFor(model => model.CustomerSettings.CountryEnabled) @@ -404,7 +406,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.PhoneEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.PhoneEnabled, new { data_toggler_for = "#pnlPhoneRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.PhoneEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.PhoneEnabled, new { data_toggler_for = "#pnlPhoneRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.PhoneEnabled) @@ -422,7 +425,8 @@ @Html.SmartLabelFor(model => model.CustomerSettings.FaxEnabled) - @Html.CheckBoxFor(model => model.CustomerSettings.FaxEnabled, new { data_toggler_for = "#pnlFaxRequired" }) + @Html.SettingEditorFor(model => model.CustomerSettings.FaxEnabled, + Html.CheckBoxFor(model => model.CustomerSettings.FaxEnabled, new { data_toggler_for = "#pnlFaxRequired" })) @Html.ValidationMessageFor(model => model.CustomerSettings.FaxEnabled) @@ -469,14 +473,15 @@ @Html.SmartLabelFor(model => model.AddressSettings.SalutationEnabled) - @Html.CheckBoxFor(model => model.AddressSettings.SalutationEnabled, new { data_toggler_for = "#pnlAddressSettingsSalutations" }) + @Html.SettingEditorFor(model => model.AddressSettings.SalutationEnabled, + Html.CheckBoxFor(model => model.AddressSettings.SalutationEnabled, new { data_toggler_for = "#pnlAddressSettingsSalutations" })) @Html.ValidationMessageFor(model => model.AddressSettings.SalutationEnabled)
            - @(Html.LocalizedEditor("setting-customer-localized", + @(Html.LocalizedEditor("setting-customer-localized", @ @@ -539,7 +545,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.StreetAddressEnabled) @@ -557,7 +564,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.StreetAddress2Enabled) @@ -575,7 +583,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.ZipPostalCodeEnabled) @@ -593,7 +602,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.CityEnabled) @@ -611,25 +621,47 @@ @Html.SmartLabelFor(model => model.AddressSettings.CountryEnabled) - - - - + + + + + + + + + + + + + + @@ -647,7 +679,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.FaxEnabled) @@ -680,8 +713,8 @@ @Html.SmartLabelFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId) @@ -701,4 +734,91 @@
            @@ -521,7 +526,8 @@ @Html.SmartLabelFor(model => model.AddressSettings.CompanyEnabled) - @Html.CheckBoxFor(model => model.AddressSettings.CompanyEnabled, new { data_toggler_for = "#pnlAddressCompanyRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.CompanyEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CompanyEnabled, new { data_toggler_for = "#pnlAddressCompanyRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.CompanyEnabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlAddressStreetAddressRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.StreetAddressEnabled, + Html.CheckBoxFor(model => model.AddressSettings.StreetAddressEnabled, new { data_toggler_for = "#pnlAddressStreetAddressRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.StreetAddressEnabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlAddressStreetAddress2Required" }) + @Html.SettingEditorFor(model => model.AddressSettings.StreetAddress2Enabled, + Html.CheckBoxFor(model => model.AddressSettings.StreetAddress2Enabled, new { data_toggler_for = "#pnlAddressStreetAddress2Required" })) @Html.ValidationMessageFor(model => model.AddressSettings.StreetAddress2Enabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlAddressZipPostalCodeRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.ZipPostalCodeEnabled, + Html.CheckBoxFor(model => model.AddressSettings.ZipPostalCodeEnabled, new { data_toggler_for = "#pnlAddressZipPostalCodeRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.ZipPostalCodeEnabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.CityEnabled, new { data_toggler_for = "#pnlAddressCityRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.CityEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CityEnabled, new { data_toggler_for = "#pnlAddressCityRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.CityEnabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.CountryEnabled, new { data_toggler_for = "#pnlAddressStateProvinceEnabled" }) + @Html.SettingEditorFor(model => model.AddressSettings.CountryEnabled, + Html.CheckBoxFor(model => model.AddressSettings.CountryEnabled, new { data_toggler_for = "#pnlAddressStateProvinceEnabled" })) @Html.ValidationMessageFor(model => model.AddressSettings.CountryEnabled)
            - @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceEnabled) - - @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceEnabled) - @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceEnabled) -
            + @Html.SmartLabelFor(model => model.AddressSettings.CountryRequired) + + @Html.SettingEditorFor(model => model.AddressSettings.CountryRequired) + @Html.ValidationMessageFor(model => model.AddressSettings.CountryRequired) +
            + @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceEnabled) + + @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceEnabled) + @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceEnabled) +
            + @Html.SmartLabelFor(model => model.AddressSettings.StateProvinceRequired) + + @Html.SettingEditorFor(model => model.AddressSettings.StateProvinceRequired) + @Html.ValidationMessageFor(model => model.AddressSettings.StateProvinceRequired) +
            @Html.SmartLabelFor(model => model.AddressSettings.PhoneEnabled) - @Html.CheckBoxFor(model => model.AddressSettings.PhoneEnabled, new { data_toggler_for = "#pnlAddressPhoneRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.PhoneEnabled, + Html.CheckBoxFor(model => model.AddressSettings.PhoneEnabled, new { data_toggler_for = "#pnlAddressPhoneRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.PhoneEnabled)
            - @Html.CheckBoxFor(model => model.AddressSettings.FaxEnabled, new { data_toggler_for = "#pnlAddressFaxRequired" }) + @Html.SettingEditorFor(model => model.AddressSettings.FaxEnabled, + Html.CheckBoxFor(model => model.AddressSettings.FaxEnabled, new { data_toggler_for = "#pnlAddressFaxRequired" })) @Html.ValidationMessageFor(model => model.AddressSettings.FaxEnabled)
            - @Html.SettingOverrideCheckbox(model => model.DateTimeSettings.DefaultStoreTimeZoneId) - @Html.DropDownListFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, Model.DateTimeSettings.AvailableTimeZones) + @Html.SettingEditorFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, + Html.DropDownListFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId, Model.DateTimeSettings.AvailableTimeZones)) @Html.ValidationMessageFor(model => model.DateTimeSettings.DefaultStoreTimeZoneId)
            +} + +@helper TabPrivacy() +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            + @Html.SmartLabelFor(model => model.PrivacySettings.EnableCookieConsent) + + @Html.SettingEditorFor(model => model.PrivacySettings.EnableCookieConsent, + Html.CheckBoxFor(model => model.PrivacySettings.EnableCookieConsent, new { data_toggler_for = "#pnlCookieConsent" })) + @Html.ValidationMessageFor(model => model.PrivacySettings.EnableCookieConsent) +
            + @(Html.LocalizedEditor("setting-privacy-localized", + @
            +
            +
            + @Html.SmartLabelFor(model => model.Locales[item].CookieConsentBadgetext) +
            +
            + @*IMPORTANT: Do not delete, this hidden element contains the id to assign localized values to the corresponding language *@ + @Html.HiddenFor(model => model.Locales[item].LanguageId) + + @Html.TextAreaFor(model => Model.Locales[item].CookieConsentBadgetext) + @Html.ValidationMessageFor(model => model.Locales[item].CookieConsentBadgetext) +
            +
            +
            + , + @
            +
            +
            + @Html.SmartLabelFor(model => model.PrivacySettings.CookieConsentBadgetext) +
            +
            + @Html.TextAreaFor(model => model.PrivacySettings.CookieConsentBadgetext) + @Html.ValidationMessageFor(model => model.PrivacySettings.CookieConsentBadgetext) +
            +
            +
            + )) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.StoreLastIpAddress) + + @Html.SettingEditorFor(model => model.PrivacySettings.StoreLastIpAddress) + @Html.ValidationMessageFor(model => model.PrivacySettings.StoreLastIpAddress) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) + + @Html.SettingEditorFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) + @Html.ValidationMessageFor(model => model.PrivacySettings.DisplayGdprConsentOnForms) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.FullNameOnContactUsRequired) + + @Html.SettingEditorFor(model => model.PrivacySettings.FullNameOnContactUsRequired) + @Html.ValidationMessageFor(model => model.PrivacySettings.FullNameOnContactUsRequired) +
            + @Html.SmartLabelFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) + + @Html.SettingEditorFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) + @Html.ValidationMessageFor(model => model.PrivacySettings.FullNameOnProductRequestRequired) +
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml index cc45b6dd6d..b1194aa108 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -52,7 +52,7 @@ @Html.SmartLabelFor(model => model.ImageDownloadTimeout) - @Html.SettingEditorFor(model => model.ImageDownloadTimeout) + @Html.SettingEditorFor(model => model.ImageDownloadTimeout, null, new { postfix = T("Time.Minutes").Text }) @Html.ValidationMessageFor(model => model.ImageDownloadTimeout) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml index dd72d82f6d..3612b4f72a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/EditorTemplates/Store.cshtml @@ -1,10 +1,9 @@ @* codehint: sm-edit (see also AllSettings.cshtml grid_onStoreEdit) *@ + placeholder='@T("Admin.Common.StoresAll")' + data-select-url="@Url.Action("AllStores", "Store")"> + Html.RenderAction("StoreScopeConfiguration", "Setting"); @@ -70,7 +61,8 @@ @Html.SmartLabelFor(model => model.StoreInformationSettings.StoreClosed) - @Html.CheckBoxFor(model => model.StoreInformationSettings.StoreClosed, new { data_toggler_for = "#pnlStoreClosedAllowForAdmins" }) + @Html.SettingEditorFor(model => model.StoreInformationSettings.StoreClosed, + Html.CheckBoxFor(model => model.StoreInformationSettings.StoreClosed, new { data_toggler_for = "#pnlStoreClosedAllowForAdmins" })) @Html.ValidationMessageFor(model => model.StoreInformationSettings.StoreClosed) @@ -103,8 +95,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.PageTitleSeoAdjustment) - @Html.SettingOverrideCheckbox(model => model.SeoSettings.PageTitleSeoAdjustment) - @Html.DropDownListForEnum(model => model.SeoSettings.PageTitleSeoAdjustment ) + @Html.EnumSettingEditorFor(model => model.SeoSettings.PageTitleSeoAdjustment) @Html.ValidationMessageFor(model => model.SeoSettings.PageTitleSeoAdjustment) @@ -140,16 +131,17 @@ @Html.SmartLabelFor(model => model.SeoSettings.MetaRobotsContent) - @Html.SettingOverrideCheckbox(model => model.SeoSettings.MetaRobotsContent) - @Html.DropDownListFor(model => model.SeoSettings.MetaRobotsContent, new List - { - new SelectListItem { Text = "index", Value = "index" }, - new SelectListItem { Text = "noindex", Value = "noindex" }, - new SelectListItem { Text = "index, follow", Value = "index, follow" }, - new SelectListItem { Text = "index, nofollow", Value = "index, nofollow" }, - new SelectListItem { Text = "noindex, follow", Value = "noindex, follow" }, - new SelectListItem { Text = "noindex, nofollow", Value = "noindex, nofollow" } - }, T("Common.Unspecified")) + @Html.SettingEditorFor(model => model.SeoSettings.MetaRobotsContent, Html.DropDownListFor(model => model.SeoSettings.MetaRobotsContent, + new List + { + new SelectListItem { Text = "index", Value = "index" }, + new SelectListItem { Text = "noindex", Value = "noindex" }, + new SelectListItem { Text = "index, follow", Value = "index, follow" }, + new SelectListItem { Text = "index, nofollow", Value = "index, nofollow" }, + new SelectListItem { Text = "noindex, follow", Value = "noindex, follow" }, + new SelectListItem { Text = "noindex, nofollow", Value = "noindex, nofollow" } + }, + T("Common.Unspecified"))) @Html.ValidationMessageFor(model => model.SeoSettings.MetaRobotsContent) @@ -167,8 +159,7 @@ @Html.SmartLabelFor(model => model.SeoSettings.CanonicalHostNameRule) - @Html.SettingOverrideCheckbox(model => model.SeoSettings.CanonicalHostNameRule) - @Html.DropDownListForEnum(model => model.SeoSettings.CanonicalHostNameRule) + @Html.EnumSettingEditorFor(model => model.SeoSettings.CanonicalHostNameRule) @Html.ValidationMessageFor(model => model.SeoSettings.CanonicalHostNameRule) @@ -234,7 +225,7 @@   - + @@ -277,11 +268,11 @@ - @Html.SmartLabelFor(model => model.SecuritySettings.ForceSslForAllPages) + @Html.SmartLabelFor(model => model.SecuritySettings.EnableHoneypotProtection) - @Html.EditorFor(model => model.SecuritySettings.ForceSslForAllPages) - @Html.ValidationMessageFor(model => model.SecuritySettings.ForceSslForAllPages) + @Html.EditorFor(model => model.SecuritySettings.EnableHoneypotProtection) + @Html.ValidationMessageFor(model => model.SecuritySettings.EnableHoneypotProtection) @@ -299,11 +290,21 @@ @Html.SmartLabelFor(model => model.CaptchaSettings.Enabled) - @Html.CheckBoxFor(model => model.CaptchaSettings.Enabled, new { data_toggler_for = "#pnlCaptchaSettings" }) + @Html.SettingEditorFor(model => model.CaptchaSettings.Enabled, + Html.CheckBoxFor(model => model.CaptchaSettings.Enabled, new { data_toggler_for = "#pnlCaptchaSettings" })) @Html.ValidationMessageFor(model => model.CaptchaSettings.Enabled) + + + @Html.SmartLabelFor(model => model.CaptchaSettings.UseInvisibleReCaptcha) + + + @Html.SettingEditorFor(model => model.CaptchaSettings.UseInvisibleReCaptcha) + @Html.ValidationMessageFor(model => model.CaptchaSettings.UseInvisibleReCaptcha) + + @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnLoginPage) @@ -376,6 +377,15 @@ @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnNewsCommentPage) + + + @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnForumPage) + + + @Html.SettingEditorFor(model => model.CaptchaSettings.ShowOnForumPage) + @Html.ValidationMessageFor(model => model.CaptchaSettings.ShowOnForumPage) + + @Html.SmartLabelFor(model => model.CaptchaSettings.ShowOnProductReviewPage) @@ -471,7 +481,8 @@ @Html.SmartLabelFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled) - @Html.CheckBoxFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled, new { data_toggler_for = "#pnlSeoFriendlyUrlsForLanguages" }) + @Html.SettingEditorFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled, + Html.CheckBoxFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled, new { data_toggler_for = "#pnlSeoFriendlyUrlsForLanguages" })) @Html.ValidationMessageFor(model => model.LocalizationSettings.SeoFriendlyUrlsForLanguagesEnabled) @@ -481,7 +492,8 @@ @Html.SmartLabelFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) - @Html.DropDownListForEnum(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) + @Html.SettingEditorFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour, + Html.DropDownListForEnum(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour)) @Html.ValidationMessageFor(model => model.LocalizationSettings.DefaultLanguageRedirectBehaviour) @@ -490,7 +502,8 @@ @Html.SmartLabelFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) - @Html.DropDownListForEnum(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) + @Html.SettingEditorFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour, + Html.DropDownListForEnum(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour)) @Html.ValidationMessageFor(model => model.LocalizationSettings.InvalidLanguageRedirectBehaviour) @@ -537,8 +550,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.Salutation) - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.Salutation) - @Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.Salutation, + Html.DropDownListFor(model => model.CompanyInformationSettings.Salutation, Model.CompanyInformationSettings.Salutations)) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.Salutation) @@ -574,8 +587,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.CompanyManagementDescription) - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.CompanyManagementDescription) - @Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.CompanyManagementDescription, + Html.DropDownListFor(model => model.CompanyInformationSettings.CompanyManagementDescription, Model.CompanyInformationSettings.ManagementDescriptions)) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CompanyManagementDescription) @@ -631,8 +644,8 @@ @Html.SmartLabelFor(model => model.CompanyInformationSettings.CountryId) - @Html.SettingOverrideCheckbox(model => model.CompanyInformationSettings.CountryId) - @Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries, new { style = "min-width:289px" } ) + @Html.SettingEditorFor(model => model.CompanyInformationSettings.CountryId, + Html.DropDownListFor(model => model.CompanyInformationSettings.CountryId, Model.CompanyInformationSettings.AvailableCountries)) @Html.ValidationMessageFor(model => model.CompanyInformationSettings.CountryId) @@ -831,7 +844,8 @@ @Html.SmartLabelFor(model => model.SocialSettings.ShowSocialLinksInFooter) - @Html.CheckBoxFor(model => model.SocialSettings.ShowSocialLinksInFooter, new { data_toggler_for = "#pnlSocialLinks" }) + @Html.SettingEditorFor(model => model.SocialSettings.ShowSocialLinksInFooter, + Html.CheckBoxFor(model => model.SocialSettings.ShowSocialLinksInFooter, new { data_toggler_for = "#pnlSocialLinks" })) @Html.ValidationMessageFor(model => model.SocialSettings.ShowSocialLinksInFooter) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml index 15e4432e86..cd2ee3bb54 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Media.cshtml @@ -1,5 +1,4 @@ @model MediaSettingsModel -@using Telerik.Web.Mvc.UI; @{ ViewBag.Title = T("Admin.Configuration.Settings.Media").Text; } @@ -37,7 +36,7 @@ @Html.SmartLabelFor(model => model.AvatarPictureSize) - @Html.SettingEditorFor(model => model.AvatarPictureSize) + @Html.SettingEditorFor(model => model.AvatarPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.AvatarPictureSize) @@ -46,7 +45,7 @@ @Html.SmartLabelFor(model => model.ProductThumbPictureSize) - @Html.SettingEditorFor(model => model.ProductThumbPictureSize) + @Html.SettingEditorFor(model => model.ProductThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductThumbPictureSize) @@ -55,7 +54,7 @@ @Html.SmartLabelFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) - @Html.SettingEditorFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) + @Html.SettingEditorFor(model => model.ProductThumbPictureSizeOnProductDetailsPage, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductThumbPictureSizeOnProductDetailsPage) @@ -64,7 +63,7 @@ @Html.SmartLabelFor(model => model.MessageProductThumbPictureSize) - @Html.SettingEditorFor(model => model.MessageProductThumbPictureSize) + @Html.SettingEditorFor(model => model.MessageProductThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MessageProductThumbPictureSize) @@ -73,7 +72,7 @@ @Html.SmartLabelFor(model => model.ProductDetailsPictureSize) - @Html.SettingEditorFor(model => model.ProductDetailsPictureSize) + @Html.SettingEditorFor(model => model.ProductDetailsPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ProductDetailsPictureSize) @@ -91,7 +90,7 @@ @Html.SmartLabelFor(model => model.AssociatedProductPictureSize) - @Html.SettingEditorFor(model => model.AssociatedProductPictureSize) + @Html.SettingEditorFor(model => model.AssociatedProductPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.AssociatedProductPictureSize) @@ -100,7 +99,7 @@ @Html.SmartLabelFor(model => model.BundledProductPictureSize) - @Html.SettingEditorFor(model => model.BundledProductPictureSize) + @Html.SettingEditorFor(model => model.BundledProductPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.BundledProductPictureSize) @@ -109,7 +108,7 @@ @Html.SmartLabelFor(model => model.CategoryThumbPictureSize) - @Html.SettingEditorFor(model => model.CategoryThumbPictureSize) + @Html.SettingEditorFor(model => model.CategoryThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CategoryThumbPictureSize) @@ -118,7 +117,7 @@ @Html.SmartLabelFor(model => model.ManufacturerThumbPictureSize) - @Html.SettingEditorFor(model => model.ManufacturerThumbPictureSize) + @Html.SettingEditorFor(model => model.ManufacturerThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.ManufacturerThumbPictureSize) @@ -127,7 +126,7 @@ @Html.SmartLabelFor(model => model.CartThumbPictureSize) - @Html.SettingEditorFor(model => model.CartThumbPictureSize) + @Html.SettingEditorFor(model => model.CartThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CartThumbPictureSize) @@ -136,7 +135,7 @@ @Html.SmartLabelFor(model => model.CartThumbBundleItemPictureSize) - @Html.SettingEditorFor(model => model.CartThumbBundleItemPictureSize) + @Html.SettingEditorFor(model => model.CartThumbBundleItemPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.CartThumbBundleItemPictureSize) @@ -145,7 +144,7 @@ @Html.SmartLabelFor(model => model.MiniCartThumbPictureSize) - @Html.SettingEditorFor(model => model.MiniCartThumbPictureSize) + @Html.SettingEditorFor(model => model.MiniCartThumbPictureSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MiniCartThumbPictureSize) @@ -154,7 +153,7 @@ @Html.SmartLabelFor(model => model.MaximumImageSize) - @Html.SettingEditorFor(model => model.MaximumImageSize) + @Html.SettingEditorFor(model => model.MaximumImageSize, null, new { postfix = T("Common.Pixel").Text }) @Html.ValidationMessageFor(model => model.MaximumImageSize) @@ -185,7 +184,7 @@ @Html.ValidationMessageFor(model => model.StorageProvider) @@ -197,7 +196,7 @@ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml index 763d28efbb..1cfc0d969d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Shipping.cshtml @@ -27,7 +27,7 @@ @Html.SmartLabelFor(model => model.FreeShippingOverXEnabled) - @Html.CheckBoxFor(model => model.FreeShippingOverXEnabled, new { data_toggler_for = "#pnlFreeShipping" }) + @Html.SettingEditorFor(model => model.FreeShippingOverXEnabled, Html.CheckBoxFor(model => model.FreeShippingOverXEnabled, new { data_toggler_for = "#pnlFreeShipping" })) @Html.ValidationMessageFor(model => model.FreeShippingOverXEnabled) @@ -37,7 +37,7 @@ @Html.SmartLabelFor(model => model.FreeShippingOverXValue) - @Html.SettingEditorFor(model => model.FreeShippingOverXValue) + @Html.SettingEditorFor(model => model.FreeShippingOverXValue, null, new { postfix = Model.PrimaryStoreCurrencyCode }) @Html.ValidationMessageFor(model => model.FreeShippingOverXValue) @@ -87,18 +87,15 @@ @Html.ValidationMessageFor(model => model.ChargeOnlyHighestProductShippingSurcharge) - + @Html.SmartLabelFor(model => model.ShippingOriginAddress) - @Html.SettingOverrideCheckbox(model => Model.ShippingOriginAddress, "#pnlShippingOriginAddress") + @Html.SettingEditorFor(model => Model.ShippingOriginAddress, @
            + @Html.EditorFor(model => model.ShippingOriginAddress, "Address") +
            ) - - - @Html.EditorFor(model => model.ShippingOriginAddress, "Address") - - } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 08e1d30c22..8eb7e14064 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -134,7 +134,7 @@ @Html.SmartLabelFor(model => model.MiniShoppingCartEnabled) - @Html.CheckBoxFor(model => model.MiniShoppingCartEnabled, new { data_toggler_for = "#pnlShowProductImagesInMiniShoppingCart" }) + @Html.SettingEditorFor(model => model.MiniShoppingCartEnabled, Html.CheckBoxFor(model => model.MiniShoppingCartEnabled, new { data_toggler_for = "#pnlShowProductImagesInMiniShoppingCart" })) @Html.ValidationMessageFor(model => model.MiniShoppingCartEnabled) @@ -237,7 +237,7 @@ @Html.SmartLabelFor(model => model.EmailWishlistEnabled) - @Html.CheckBoxFor(model => model.EmailWishlistEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailWishlist" }) + @Html.SettingEditorFor(model => model.EmailWishlistEnabled, Html.CheckBoxFor(model => model.EmailWishlistEnabled, new { data_toggler_for = "#pnlAllowAnonymousUsersToEmailWishlist" })) @Html.ValidationMessageFor(model => model.EmailWishlistEnabled) @@ -255,7 +255,7 @@ @helper TabCheckoutSettings() { - +
            @@ -305,16 +304,13 @@ @Html.SmartLabelFor(model => model.ThirdPartyEmailHandOver)
            @@ -277,8 +277,7 @@ @Html.SmartLabelFor(model => model.NewsLetterSubscription)
            - @Html.SettingOverrideCheckbox(model => Model.NewsLetterSubscription) - @Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscriptions) + @Html.SettingEditorFor(model => model.NewsLetterSubscription, Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscriptions)) @Html.ValidationMessageFor(model => model.NewsLetterSubscription)
            - @Html.SettingOverrideCheckbox(model => Model.ThirdPartyEmailHandOver) - @Html.DropDownListFor(model => model.ThirdPartyEmailHandOver, Model.AvailableThirdPartyEmailHandOver) + @Html.SettingEditorFor(model => model.ThirdPartyEmailHandOver, Html.DropDownListFor(model => model.ThirdPartyEmailHandOver, Model.AvailableThirdPartyEmailHandOver)) @Html.ValidationMessageFor(model => model.ThirdPartyEmailHandOver)
            -

            - @(Html.LocalizedEditor("setting-shopping-cart-localized", @ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml index ddd59a8d39..4c99912b3b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/StoreScopeConfiguration.cshtml @@ -14,13 +14,12 @@ @if (Model.StoreId > 0) { - - + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml index 5dad48fb31..da8451c205 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml @@ -1,6 +1,4 @@ -@using Telerik.Web.Mvc.UI -@using SmartStore.Core.Domain.Tax - +@using SmartStore.Core.Domain.Tax @model TaxSettingsModel @{ ViewBag.Title = T("Admin.Configuration.Settings.Tax").Text; @@ -67,11 +65,10 @@ - + - + - + - - - @@ -184,7 +176,7 @@ @Html.SmartLabelFor(model => model.ShippingIsTaxable) @@ -203,8 +195,7 @@ @Html.SmartLabelFor(model => model.ShippingTaxClassId) @@ -214,7 +205,8 @@ @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeIsTaxable) @@ -233,8 +225,8 @@ @Html.SmartLabelFor(model => model.PaymentMethodAdditionalFeeTaxClassId) @@ -249,7 +241,7 @@ @Html.SmartLabelFor(model => model.EuVatEnabled) @@ -259,8 +251,7 @@ @Html.SmartLabelFor(model => model.EuVatShopCountryId) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml index 76ae0c2bca..15061f8537 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Address.cshtml @@ -1,33 +1,7 @@ @model AddressModel -@if (Model.CountryEnabled && Model.StateProvinceEnabled) -{ - -} @Html.HiddenFor(model => model.Id) +@Html.HiddenFor(model => model.TitleEnabled) @Html.HiddenFor(model => model.FirstNameEnabled) @Html.HiddenFor(model => model.FirstNameRequired) @Html.HiddenFor(model => model.LastNameEnabled) @@ -51,7 +25,20 @@ @Html.HiddenFor(model => model.PhoneRequired) @Html.HiddenFor(model => model.FaxEnabled) @Html.HiddenFor(model => model.FaxRequired) +
              - +   +
            + + +
            @Html.SmartLabelFor(model => model.TaxDisplayType) - @Html.SettingOverrideCheckbox(model => Model.TaxDisplayType) - @Html.DropDownListForEnum(model => model.TaxDisplayType) - @Html.ValidationMessageFor(model => model.TaxDisplayType) - + @Html.EnumSettingEditorFor(model => model.TaxDisplayType) + @Html.ValidationMessageFor(model => model.TaxDisplayType) +
            @@ -145,25 +142,21 @@ @Html.SmartLabelFor(model => model.TaxBasedOn) - @Html.SettingOverrideCheckbox(model => Model.TaxBasedOn) - @Html.DropDownListForEnum(model => model.TaxBasedOn) - @Html.ValidationMessageFor(model => model.TaxBasedOn) - + @Html.EnumSettingEditorFor(model => model.TaxBasedOn) + @Html.ValidationMessageFor(model => model.TaxBasedOn) +
            @Html.SmartLabelFor(model => model.DefaultTaxAddress) - @Html.SettingOverrideCheckbox(model => Model.DefaultTaxAddress, "#pnlDefaultTaxAddress") + @Html.SettingEditorFor(model => Model.DefaultTaxAddress, @
            + @Html.EditorFor(x => x.DefaultTaxAddress, "Address") +
            )
            - @Html.EditorFor(x => x.DefaultTaxAddress, "Address") -

            @@ -174,8 +167,7 @@ @Html.SmartLabelFor(model => model.AuxiliaryServicesTaxingType)
            - @Html.SettingOverrideCheckbox(model => Model.AuxiliaryServicesTaxingType) - @Html.DropDownListFor(model => model.AuxiliaryServicesTaxingType, Model.AvailableAuxiliaryServicesTaxTypes) + @Html.EnumSettingEditorFor(model => model.AuxiliaryServicesTaxingType) @Html.ValidationMessageFor(model => model.AuxiliaryServicesTaxingType)
            - @Html.CheckBoxFor(model => model.ShippingIsTaxable, new { data_toggler_for = "#pnlShippingIsTaxable" }) + @Html.SettingEditorFor(model => model.ShippingIsTaxable, Html.CheckBoxFor(model => model.ShippingIsTaxable, new { data_toggler_for = "#pnlShippingIsTaxable" })) @Html.ValidationMessageFor(model => model.ShippingIsTaxable)
            - @Html.SettingOverrideCheckbox(model => Model.ShippingTaxClassId) - @Html.DropDownListFor(model => model.ShippingTaxClassId, Model.ShippingTaxCategories, T("Common.PleaseSelect").Text) + @Html.SettingEditorFor(model => model.ShippingTaxClassId, Html.DropDownListFor(model => model.ShippingTaxClassId, Model.ShippingTaxCategories, T("Common.PleaseSelect"))) @Html.ValidationMessageFor(model => model.ShippingTaxClassId)
            - @Html.CheckBoxFor(model => model.PaymentMethodAdditionalFeeIsTaxable, new { data_toggler_for = "#pnlPaymentMethodAdditionalFeeIsTaxable" }) + @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeIsTaxable, + Html.CheckBoxFor(model => model.PaymentMethodAdditionalFeeIsTaxable, new { data_toggler_for = "#pnlPaymentMethodAdditionalFeeIsTaxable" })) @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeIsTaxable)
            - @Html.SettingOverrideCheckbox(model => Model.PaymentMethodAdditionalFeeTaxClassId) - @Html.DropDownListFor(model => model.PaymentMethodAdditionalFeeTaxClassId, Model.PaymentMethodAdditionalFeeTaxCategories, T("Common.PleaseSelect").Text) + @Html.SettingEditorFor(model => model.PaymentMethodAdditionalFeeTaxClassId, + Html.DropDownListFor(model => model.PaymentMethodAdditionalFeeTaxClassId, Model.PaymentMethodAdditionalFeeTaxCategories, T("Common.PleaseSelect"))) @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeTaxClassId)
            - @Html.CheckBoxFor(model => model.EuVatEnabled, new { data_toggler_for = "#pnlEuVat" }) + @Html.SettingEditorFor(model => model.EuVatEnabled, Html.CheckBoxFor(model => model.EuVatEnabled, new { data_toggler_for = "#pnlEuVat" })) @Html.ValidationMessageFor(model => model.EuVatEnabled)
            - @Html.SettingOverrideCheckbox(model => Model.EuVatShopCountryId) - @Html.DropDownListFor(model => model.EuVatShopCountryId, Model.EuVatShopCountries, T("Admin.Address.SelectCountry").Text) + @Html.SettingEditorFor(model => model.EuVatShopCountryId, Html.DropDownListFor(model => model.EuVatShopCountryId, Model.EuVatShopCountries, T("Admin.Address.SelectCountry"))) @Html.ValidationMessageFor(model => model.EuVatShopCountryId)
            + @if (Model.TitleEnabled) + { + + + + + } @if (Model.FirstNameEnabled) { @@ -119,8 +106,21 @@ @Html.SmartLabelFor(model => model.CountryId) } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml index ce6b893eef..97cf495390 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/Download.cshtml @@ -5,56 +5,86 @@ @using SmartStore.Utilities; @functions { - bool? _minimalMode = null; - string _fieldName = null; - - private bool MinimalMode - { - get - { - if (!_minimalMode.HasValue) - { - _minimalMode = ViewData.ContainsKey("minimalMode") ? ViewData["minimalMode"].Convert() : false; - } - return _minimalMode.Value; - } - } + bool? _minimalMode = null; + string _fieldName = null; + int _entityId = 0; + string _entityName = null; - private string FieldName - { - get - { - if (_fieldName == null) - { - _fieldName = ViewData.ContainsKey("fieldName") ? ViewData["fieldName"].Convert() : ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); - } - return _fieldName; - } - } + private bool MinimalMode + { + get + { + if (!_minimalMode.HasValue) + { + _minimalMode = ViewData.ContainsKey("minimalMode") ? ViewData["minimalMode"].Convert() : false; + } + return _minimalMode.Value; + } + } + + private string FieldName + { + get + { + if (_fieldName == null) + { + _fieldName = ViewData.ContainsKey("fieldName") ? ViewData["fieldName"].Convert() : ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + } + return _fieldName; + } + } + + private int EntityId + { + get + { + if (_entityId == 0) + { + _entityId = ViewData.ContainsKey("entityId") ? ViewData["entityId"].Convert() : 0; + } + return _entityId; + } + } + + private string EntityName + { + get + { + if (_entityName == null) + { + _entityName = ViewData.ContainsKey("entityName") ? ViewData["entityName"].Convert() : ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + } + return _entityName; + } + } } @{ - var random = CommonHelper.GenerateRandomInteger(); - var clientId = "download-editor-" + random; - var downloadService = EngineContext.Current.Resolve(); - var download = downloadService.GetDownloadById(Model.GetValueOrDefault()); - var initiallyShowUrlPanel = false; - var hasFile = false; - var downloadUrl = ""; - if (download != null) { - downloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = download.Id }); - initiallyShowUrlPanel = !MinimalMode && download.UseDownloadUrl; - hasFile = !download.UseDownloadUrl; - } + var random = CommonHelper.GenerateRandomInteger(); + var clientId = "download-editor-" + random; + var downloadService = EngineContext.Current.Resolve(); + var download = downloadService.GetDownloadById(Model.GetValueOrDefault()); + var fileName = string.Empty; + var initiallyShowUrlPanel = false; + var hasFile = false; + var downloadUrl = ""; + if (download != null) { + downloadUrl = Url.Action("DownloadFile", "Download", new { downloadId = download.Id }); + initiallyShowUrlPanel = !MinimalMode && download.UseDownloadUrl; + hasFile = !download.UseDownloadUrl; + fileName = string.IsNullOrWhiteSpace(download.Filename) || download.Filename.IsCaseInsensitiveEqual("undefined") + ? download.Id.ToString() + : string.Concat(download.Filename, download.Extension); + } - Html.AddScriptParts(true, "~/Administration/Scripts/smartstore.download.js"); + Html.AddScriptParts(true, "~/Administration/Scripts/smartstore.download.js"); }
            + data-upload-url="@Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })" + data-delete-url="@Url.Action("DeleteDownload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })"> @@ -96,15 +126,15 @@ { }
            @(Html.SmartStore().FileUploader() - .UploadUrl(Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName })) + .UploadUrl(Url.Action("AsyncUpload", "Download", new { minimalMode = MinimalMode, fieldName = FieldName, entityId = EntityId, entityName = EntityName, area = "Admin" })) .AcceptedFileTypes("") .IconCssClass(MinimalMode ? "fa fa-upload" : "") .ButtonOutlineStyle(hasFile) @@ -126,7 +156,7 @@ @{ var value = download != null ? download.DownloadUrl : ""; }
            - + @T("Common.Save") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml index 3400fa2ef0..01cceea655 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/ModelTree.cshtml @@ -1,45 +1,84 @@ @model TreeNode +@using Telerik.Web.Mvc.UI; +@using Telerik.Web.Mvc.UI.Fluent; @using SmartStore.Collections; @using SmartStore.Services.Messages; -@helper TokenSelector(TreeNode root) +@helper TokenSelector() { - + + + @T("Admin.Common.ChooseToken") + } -@helper TokenList(TreeNode node) -{ - +@{Html.SmartStore().Window() + .Name("addtoken-window") + .Size(WindowSize.FlexSmall) + .Title(T("Admin.Common.ChooseToken")) + .Content( + @ + @(Html.Telerik().TreeView() + .Name("token-treeview") + .Items(tv => + { + AddItemsToModelTree(tv, Model); + }) + .ShowLines(true) + .ClientEvents(events => + { + if (Model != null) + { + events.OnSelect("onNodeSelect"); + } + }) + .DragAndDrop(false)) + ) + .FooterContent(@ + + + ) + .Render(); +} + +@functions { + public static void AddItemsToModelTree(TreeViewItemFactory tv, TreeNode root) + { + if (root != null) + { + foreach (var node in root.Children) + { + + List tokens = new List(); + var curNode = node; + while (!curNode.IsRoot) + { + tokens.Insert(0, curNode.Value.Name); + curNode = curNode.Parent; + } + + tv.Add() + .Text(node.Value.Name) + .Value(String.Join(".", tokens)) + //.HtmlAttributes(new { _data-leaf = node.IsLeaf.ToString() }) + .HtmlAttributes(new { isleaf = node.IsLeaf.ToString().ToLower() }) + .Items(tvc => + { + + if (!node.IsLeaf) + { + AddItemsToModelTree(tvc, node); + } + }); + } + } + } } @if (Model == null || !Model.HasChildren) @@ -50,30 +89,69 @@ } else { - @TokenSelector(Model) + @TokenSelector() } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml deleted file mode 100644 index dce7c51a8a..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.SummerNote.cshtml +++ /dev/null @@ -1,46 +0,0 @@ -@model String -@using SmartStore.Web.Framework.UI; -@{ - var availableLangs = new string[] { "de-DE", "en-US" }; - var lang = WorkContext.WorkingLanguage.LanguageCulture; - if (!availableLangs.Contains(lang)) - { - lang = "en-US"; - } - - Html.AddScriptParts(true, - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror.min.js", - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/mode/xml/xml.min.js", - "//cdnjs.cloudflare.com/ajax/libs/codemirror/2.36.0/formatting.min.js", - "~/Content/editors/summernote/summernote.js", - "~/Content/editors/summernote/globalinit.js"); - - if (lang != "en-US") - { - Html.AddScriptParts(true, "~/Content/editors/summernote/langs/summernote-{0}.js".FormatInvariant(lang)); - } - - Html.AppendCssFileParts(true, - "//cdnjs.cloudflare.com/ajax/libs/codemirror/3.20.0/codemirror.min.css", - "~/Content/editors/summernote/summernote.css"); -} - - - -@Html.TextArea(string.Empty, /* Name suffix */ - (string)ViewData.TemplateInfo.FormattedModelValue, /* Initial value */ - new { @class = "summernote-editor", data_upload_url = Url.Action("UploadImageAjax", "Media") } -) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml deleted file mode 100644 index 8dd130fe83..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ /dev/null @@ -1,65 +0,0 @@ -@model String -@using SmartStore.Web.Framework.UI; -@using SmartStore.Web.Framework.Theming; - -@functions { - private bool FullPage - { - get - { - if (ViewData.ContainsKey("fullPage")) - { - var x = ViewData["fullPage"].Convert(); - return x; - } - return false; - } - } -} - -@{ - var availableLangs = new string[] { "de", "en" }; - var lang = WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToLower(); - if (!availableLangs.Contains(lang)) - { - lang = "en"; - } - - Html.AddScriptParts(true, "~/Content/editors/ckeditor/ckeditor.js"); - - var themeCssPath = Url.ThemeAwareContent("Content/theme.scss"); -} - - - -@Html.TextArea( - string.Empty, /* Name suffix */ - (string)ViewData.TemplateInfo.FormattedModelValue, /* Initial value */ - new { @class = "", style = "", data_fullpage = FullPage.ToString().ToLower(), data_upload_url = Url.Action("UploadImageAjax", "Media") } -) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml new file mode 100644 index 0000000000..3a0fa42651 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/SelectList.cshtml @@ -0,0 +1,28 @@ +@using SmartStore.Web.Framework; +@{ + var multiple = GetValue("multiple"); + var dataTags = GetValue("data_tags"); + var selectOptions = GetValue>("selectOptions"); + var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); + var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); +} + + +@functions +{ + private TProperty GetValue(string key, TProperty defaultValue = default(TProperty)) + { + if (ViewData.ContainsKey(key)) + { + return (TProperty)ViewData[key]; + } + + return defaultValue; + } +} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml index 069417c230..3844898223 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml @@ -1,282 +1,83 @@ -@model string +@model string[] -@{ - var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); - var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); +@using Newtonsoft.Json.Linq; +@using SmartStore.Utilities; - string availableZones = ""; - if (ViewData.ContainsKey("AvailableZones")) - { - availableZones = (ViewData["AvailableZones"] as string) ?? ""; - } +@functions { + private string[] Value + { + get + { + string[] value = null; + if (ViewData.Model != null) + { + value = ViewData.Model; + } + return value; + } + } } - +@{ + var id = ViewData.TemplateInfo.GetFullHtmlFieldId(string.Empty); + var name = ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty); + var widgetProvider = EngineContext.Current.Resolve(); + var jsonZones = (JObject)widgetProvider.GetAllKnownWidgetZones(); + + var areas = + from p in jsonZones["WidgetZonesAreas"] + select p; + + var zones = new List(); + var userDefinedZones = Value != null ? Value.ToList() : new List(); + var selectedZones = new HashSet(userDefinedZones, StringComparer.OrdinalIgnoreCase); - + // remove item from userdefined zones (it is a system zone) + userDefinedZones.Remove(zoneName); + } + } + // add userdefined zones to available zones + var userDefinedGroup = new SelectListGroup { Name = T("Admin.WidgetZones.UserDefined").Text }; + userDefinedZones.Reverse(); + + foreach (var zoneName in userDefinedZones) + { + zones.Insert(0, new SelectListItem + { + Text = zoneName, + Value = zoneName, + Selected = true, + Group = userDefinedGroup + }); + } + + // get selected items + var selectedValues = zones.Where(x => x.Selected == true).Select(x => x.Value.ToString()).ToArray(); +} +@Html.ListBox("", + new MultiSelectList(zones, "Value", "Text", "Group.Name", selectedValues), + new { @class = "form-control x-select2-hidden-accessible", multiple = "multiple", size = 1, data_tags = "true", value = Value, name = name, id = id }) \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml index 06c60c05b1..f1cdda6cfa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminLayout.cshtml @@ -27,9 +27,9 @@
            @if (ViewData["warning.panel.message"] != null) { -
            - +
            @Html.Raw(ViewData["warning.panel.message"]) +
            } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml index e7f8bd5936..837caa3c50 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Layouts/_AdminRoot.cshtml @@ -4,8 +4,6 @@ @using System.Web.Hosting; @{ - var currentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture; - // page title string adminPageTitle = ""; if (!string.IsNullOrWhiteSpace(ViewBag.Title)) @@ -14,89 +12,84 @@ } adminPageTitle += T("Admin.PageTitle").Text; - var volatileJsRoot = "~/Administration/Scripts/"; - var vendorsRoot = "~/Administration/Content/vendors/"; - var flexJsRoot = "~/Themes/Flex/Scripts/"; - var flexCssRoot = "~/Themes/Flex/Content/css/"; + var adminJsRoot = "~/Administration/Scripts/"; + var vendorsRoot = "~/Content/vendors/"; var contentRoot = "~/Content/"; - var scriptsRoot = "~/Scripts/"; + var jsRoot = "~/Scripts/"; // add css assets - var telerikCssRoot = "~/Content/2012.2.607/"; + var telerikCssRoot = "~/Administration/Content/telerik/css/2012.2.607/"; + Html.AppendCssFileParts( - flexCssRoot + "fontastic.css", - contentRoot + "font-awesome.css", + contentRoot + "fontastic/fontastic.css", + vendorsRoot + "font-awesome/font-awesome.css", telerikCssRoot + "telerik.common.css", vendorsRoot + "pnotify/css/pnotify.css", vendorsRoot + "pnotify/css/pnotify.mobile.css", - vendorsRoot + "pnotify/css/pnotify.buttons.css", - "~/Administration/Content/theme.scss"); + vendorsRoot + "pnotify/css/pnotify.buttons.css"); + + // Theme + Html.AddCssFileParts(true, "~/Administration/Content/theme.scss"); + + if (WorkContext.WorkingLanguage.Rtl) + { + Html.AddCssFileParts(true, + telerikCssRoot + "telerik.rtl.css", + "~/Administration/Content/theme-rtl.scss"); + } // add js assets (Head) Html.AppendScriptParts(ResourceLocation.Head, - scriptsRoot + "modernizr.js", - // Telerik doesn't like jQuery 1.9+, a shame! - //"~/Administration/Scripts/jquery-1.8.3.js", - volatileJsRoot + "jquery-3.2.1.js", - volatileJsRoot + "jquery-migrate-3.2.1.js", - volatileJsRoot + "jquery-shims.js"); - - // add js assets (Foot) - var bootstrap4JsRoot = "~/Administration/Content/bs4/js/"; + vendorsRoot + "modernizr/modernizr.js", + vendorsRoot + "jquery/jquery-3.2.1.js", + //vendorsRoot + "jquery/jquery-migrate-3.0.0.js", + adminJsRoot + "jquery-shims.js"); Html.AppendScriptParts(ResourceLocation.Foot, - // jQuery UI Core - scriptsRoot + "jquery-ui/effect.js", - scriptsRoot + "jquery-ui/effect-transfer.js", - scriptsRoot + "jquery-ui/effect-shake.js", - scriptsRoot + "jquery-ui/position.js", - // jQuery Validation - volatileJsRoot + "jquery.unobtrusive-ajax.js", - volatileJsRoot + "jquery.validate.js", - volatileJsRoot + "jquery.validate.unobtrusive.js", - volatileJsRoot + "jquery.validate.unobtrusive.custom.js", - // SmartStore system - scriptsRoot + "underscore.js", - scriptsRoot + "underscore.string.js", - scriptsRoot + "underscore.mixins.js", - scriptsRoot + "smartstore.system.js", - scriptsRoot + "smartstore.jquery.utils.js", - vendorsRoot + "moment/moment.js", - scriptsRoot + "smartstore.globalization.js", - // Common - volatileJsRoot + "smartstore.viewport.js", - scriptsRoot + "jquery.ba-outside-events.js", - scriptsRoot + "jquery.preload.js", - scriptsRoot + "jquery.menu-aim.js", - scriptsRoot + "smartstore.doAjax.js", - scriptsRoot + "smartstore.entitypicker.js", - scriptsRoot + "jquery.addeasing.js", - scriptsRoot + "smartstore.eventbroker.js", - scriptsRoot + "smartstore.hacks.js", - volatileJsRoot + "smartstore.common.js", - // Bootstrap - contentRoot + "bs4/js/bootstrap.bundle.js", // Vendors + vendorsRoot + "underscore/underscore.js", + vendorsRoot + "underscore/underscore.string.js", + vendorsRoot + "jquery/jquery.addeasing.js", + vendorsRoot + "jquery-ui/effect.js", + vendorsRoot + "jquery-ui/effect-transfer.js", + vendorsRoot + "jquery-ui/position.js", + vendorsRoot + "jquery/jquery.unobtrusive-ajax.js", + vendorsRoot + "jquery/jquery.validate.js", + vendorsRoot + "jquery/jquery.validate.unobtrusive.js", + vendorsRoot + "jquery/jquery.scrollTo.js", + vendorsRoot + "jquery/jquery.sortable.js", + vendorsRoot + "moment/moment.js", vendorsRoot + "datetimepicker/js/tempusdominus-bootstrap-4.js", + vendorsRoot + "colorpicker/js/bootstrap-colorpicker.js", + vendorsRoot + "colorpicker/js/bootstrap-colorpicker-globalinit.js", vendorsRoot + "select2/js/select2.js", - // Shared UI - volatileJsRoot + "smartstore.selectwrapper.js", - scriptsRoot + "smartstore.throbber.js", - scriptsRoot + "smartstore.thumbzoomer.js", - scriptsRoot + "smartstore.shrinkmenu.js", - scriptsRoot + "smartstore.scrollbutton.js", vendorsRoot + "pnotify/js/pnotify.js", vendorsRoot + "pnotify/js/pnotify.mobile.js", vendorsRoot + "pnotify/js/pnotify.buttons.js", vendorsRoot + "pnotify/js/pnotify.animate.js", - scriptsRoot + "jquery.scrollTo.js", - scriptsRoot + "jquery.sortable.js", + contentRoot + "bs4/js/bootstrap.bundle.js", + // Common + jsRoot + "underscore.mixins.js", + jsRoot + "smartstore.system.js", + jsRoot + "smartstore.touchevents.js", + jsRoot + "smartstore.jquery.utils.js", + jsRoot + "smartstore.globalization.js", + jsRoot + "jquery.validate.unobtrusive.custom.js", + jsRoot + "smartstore.viewport.js", + jsRoot + "smartstore.doajax.js", + jsRoot + "smartstore.eventbroker.js", + jsRoot + "smartstore.hacks.js", + jsRoot + "smartstore.common.js", + jsRoot + "smartstore.selectwrapper.js", + jsRoot + "smartstore.throbber.js", + jsRoot + "smartstore.thumbzoomer.js", + jsRoot + "smartstore.entitypicker.js", // Admin - "~/Administration/Scripts/admin.common.js", - "~/Administration/Scripts/admin.globalinit.js"); + adminJsRoot + "admin.common.js", + adminJsRoot + "admin.globalinit.js"); } - + @adminPageTitle @@ -110,7 +103,7 @@ @Html.SmartCssFiles(this.Url, ResourceLocation.Head) @Html.SmartScripts(this.Url, ResourceLocation.Head) - + @Html.CustomHead() @@ -121,10 +114,13 @@ @{ Html.RenderZone("start"); } @RenderBody() + @{ Html.RenderZone("aftercontent"); } @RenderSection("foot", required: false) @Html.SmartCssFiles(this.Url, ResourceLocation.Foot) @Html.SmartScripts(this.Url, ResourceLocation.Foot) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "select2/js/i18n", "*.js", null) + @Html.LocalizationScript(WorkContext.WorkingLanguage.UniqueSeoCode, vendorsRoot + "moment/locale", "*.js", null) @(Html.Telerik().ScriptRegistrar() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml index 833d27649a..b616b32317 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/CsvConfiguration.cshtml @@ -44,7 +44,7 @@ @Html.SmartLabelFor(x => x.Delimiter)
            @@ -53,7 +53,7 @@ @Html.SmartLabelFor(x => x.Quote) @@ -62,7 +62,7 @@ @Html.SmartLabelFor(x => x.Escape) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Menu.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Menu.cshtml index 421719161c..d7c8807560 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Menu.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Partials/Menu.cshtml @@ -59,7 +59,7 @@ var itemUrl = GetNodeLink(node); - @@ -55,7 +55,7 @@ @Html.SmartLabelFor(model => model.Description) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml index f1e076a0b6..a1fdc92b37 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentCarts.cshtml @@ -19,48 +19,48 @@
            @(Html.Telerik().Grid() - .Name("carts-grid") - .Columns(columns => - { - columns.Bound(x => x.CustomerId) - .ClientTemplate("\"><#= CustomerEmail #>"); - columns.Bound(x => x.TotalItems) + .Name("carts-grid") + .Columns(columns => + { + columns.Bound(x => x.CustomerId) + .ClientTemplate("\"><#= CustomerEmail #>"); + columns.Bound(x => x.TotalItems) .Centered() .Width(100); - }) - .DetailView(details => details.ClientTemplate( - Html.Telerik().Grid() - .Name("cartitems-grid-<#= CustomerId #>") - .Columns(columns => - { - columns.Bound(sci => sci.ProductName) + }) + .DetailView(details => details.ClientTemplate( + Html.Telerik().Grid() + .Name("cartitems-grid-<#= CustomerId #>") + .Columns(columns => + { + columns.Bound(sci => sci.ProductName) .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(sci => sci.Quantity) + columns.Bound(sci => sci.Store) + .Width(220); + columns.Bound(sci => sci.UpdatedOn) + .Width(140); + columns.Bound(sci => sci.Quantity) .Centered() .Width(100); - columns.Bound(sci => sci.UnitPrice) + columns.Bound(sci => sci.UnitPrice) .RightAlign() .Width(120); - columns.Bound(sci => sci.Total) + columns.Bound(sci => sci.Total) .RightAlign() .Width(120); - columns.Bound(sci => sci.Store) - .Width(220); - columns.Bound(sci => sci.UpdatedOn) - .Width(140); - }) - .DataBinding(dataBinding => dataBinding.Ajax() - .Select("GetCartDetails", "ShoppingCart", new - { - customerId = - "<#= CustomerId #>" - })) - .ToHtmlString() - ) - ) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentCarts", "ShoppingCart")) + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("GetCartDetails", "ShoppingCart", new + { + customerId = + "<#= CustomerId #>" + })) + .ToHtmlString() + ) + ) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentCarts", "ShoppingCart")) .PreserveGridState() - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml index d623cf8136..056567da5c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ShoppingCart/CurrentWishlists.cshtml @@ -19,48 +19,48 @@
            @(Html.Telerik().Grid() - .Name("carts-grid") - .Columns(columns => - { - columns.Bound(x => x.CustomerId) - .ClientTemplate("\"><#= CustomerEmail #>"); - columns.Bound(x => x.TotalItems) + .Name("carts-grid") + .Columns(columns => + { + columns.Bound(x => x.CustomerId) + .ClientTemplate("\"><#= CustomerEmail #>"); + columns.Bound(x => x.TotalItems) .Centered() .Width(100); - }) - .DetailView(details => details.ClientTemplate( - Html.Telerik().Grid() - .Name("cartitems-grid-<#= CustomerId #>") - .Columns(columns => - { - columns.Bound(sci => sci.ProductName) + }) + .DetailView(details => details.ClientTemplate( + Html.Telerik().Grid() + .Name("cartitems-grid-<#= CustomerId #>") + .Columns(columns => + { + columns.Bound(sci => sci.ProductName) .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(sci => sci.Quantity) + columns.Bound(sci => sci.Store) + .Width(220); + columns.Bound(sci => sci.UpdatedOn) + .Width(140); + columns.Bound(sci => sci.Quantity) .Centered() .Width(100); - columns.Bound(sci => sci.UnitPrice) + columns.Bound(sci => sci.UnitPrice) .RightAlign() .Width(120); - columns.Bound(sci => sci.Total) + columns.Bound(sci => sci.Total) .RightAlign() .Width(120); - columns.Bound(sci => sci.Store) - .Width(220); - columns.Bound(sci => sci.UpdatedOn) - .Width(140); - }) - .DataBinding(dataBinding => dataBinding.Ajax() - .Select("GetWishlistDetails", "ShoppingCart", new - { - customerId = - "<#= CustomerId #>" - })) - .ToHtmlString() - ) - ) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentWishlists", "ShoppingCart")) + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("GetWishlistDetails", "ShoppingCart", new + { + customerId = + "<#= CustomerId #>" + })) + .ToHtmlString() + ) + ) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("CurrentWishlists", "ShoppingCart")) .PreserveGridState() - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
            } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdate.cshtml index a594117815..db938388c9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/SpecificationAttribute/_CreateOrUpdate.cshtml @@ -160,7 +160,8 @@ }) .Columns(columns => { - columns.Bound(x => x.Name); + columns.Bound(x => x.Name) + .Width("50%"); //TODO display localized values here columns.Bound(x => x.Alias); columns.Bound(x => x.NumberValue) @@ -169,17 +170,12 @@ columns.Bound(x => x.DisplayOrder) .Width(180) .Centered(); - columns.Bound(x => x.Id) - .Width(120) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=specificationattribute-form'); return false;\" class='btn btn-secondary' />") - .Title(T("Admin.Common.Edit").Text); columns.Command(commands => { + commands.Custom("edit-spec-attr").Text(T("Common.Edit")); commands.Delete().Localize(T); }) - .Width(120) - .Title(T("Admin.Common.Delete").Text); + .HtmlAttributes(new { align = "right" }); }) .ToolBar(commands => commands.Template(SpecificationAttributeOptionGridCommands)) .DataBinding(dataBinding => @@ -214,5 +210,17 @@ //return false to don't reload a page return false; }); + + $('#specificationattributeoptions-grid').on('click', '.t-grid-edit-spec-attr', function (e) { + e.preventDefault(); + var grid = $('#specificationattributeoptions-grid').data('tGrid'); + var tr = $(this).closest('tr'); + var id = grid.dataItem(tr).Id; + var href = "@Url.Content("~/Admin/SpecificationAttribute/OptionEditPopup/")" + id + "?btnId=btnRefresh&formId=specificationattribute-form"; + + openPopup(href); + + return false; + }); }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml index 545c816618..3e158a9359 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Store/_CreateOrUpdate.cshtml @@ -76,15 +76,26 @@ @Html.ValidationMessageFor(model => model.SslEnabled) - - - - + + + + + + + + + + ",""],legend:[1,"
            ","
            "],thead:[1,"
            + @Html.SmartLabelFor(model => model.Title) + + @Html.EditorFor(model => model.Title) + @Html.ValidationMessageFor(model => model.Title) +
            - @Html.DropDownListFor(model => model.CountryId, Model.AvailableCountries, T("Admin.Address.SelectCountry").Text) + @Html.DropDownList("CountryId", Model.AvailableCountries, + new + { + @class = "form-control country-input country-selector", + data_region_control_selector = "#" + Html.FieldIdFor(m => m.StateProvinceId), + data_states_ajax_url = Url.Action("GetStatesByCountryId", "Country"), + data_addEmptyStateIfRequired = "true" + }) @Html.ValidationMessageFor(model => model.CountryId) + +
            - @Html.TextBoxFor(x => x.Delimiter, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Delimiter, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Delimiter)
            - @Html.TextBoxFor(x => x.Quote, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Quote, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Quote)
            - @Html.TextBoxFor(x => x.Escape, new { style = "width: 30px;", maxlength = "2" }) + @Html.TextBoxFor(x => x.Escape, new { maxlength = "2" }) @Html.ValidationMessageFor(x => x.Escape)
            - @Html.EditorFor(model => model.Locales[item].Description, Html.RichEditorFlavor()) + @Html.EditorFor(model => model.Locales[item].Description, "Html") @Html.ValidationMessageFor(model => model.Locales[item].Description)
            - @Html.EditorFor(model => model.Description, Html.RichEditorFlavor()) + @Html.EditorFor(model => model.Description, "Html") @Html.ValidationMessageFor(model => model.Description)
            - @Html.SmartLabelFor(model => model.SecureUrl) - - @Html.EditorFor(model => model.SecureUrl) - @Html.ValidationMessageFor(model => model.SecureUrl) -
            + @Html.SmartLabelFor(model => model.SecureUrl) + + @Html.EditorFor(model => model.SecureUrl) + @Html.ValidationMessageFor(model => model.SecureUrl) +
            + @Html.SmartLabelFor(model => model.ForceSslForAllPages) + + @Html.EditorFor(model => model.ForceSslForAllPages) + @Html.ValidationMessageFor(model => model.ForceSslForAllPages) +
            @Html.SmartLabelFor(model => model.HtmlBodyId) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml index 5dc837f29c..8fdd5069ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Tax/Categories.cshtml @@ -18,21 +18,18 @@ }) .Columns(columns => { - columns.Bound(x => x.Name); + columns.Bound(x => x.Name) + .Width("70%"); columns.Bound(x => x.DisplayOrder) .Centered() - .Width(120); + .Width("10%"); columns.Command(commands => { commands.Edit().Localize(T); commands.Delete().Localize(T); - }).Width(240); - - }) - .ToolBar(x => x.Insert()) - .Editable(x => - { - x.Mode(GridEditMode.InLine); + }) + .HtmlAttributes(new { align = "right", @class= "omega" }) + .Width("20%"); }) .DataBinding(dataBinding => { @@ -41,6 +38,8 @@ .Delete("CategoryDelete", "Tax") .Insert("CategoryAdd", "Tax"); }) + .ToolBar(x => x.Insert()) + .Editable(x => { x.Mode(GridEditMode.InLine); }) .ClientEvents(x => x.OnError("grid_onError")) .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml index 58dc521a0b..e08dfc2ba2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Theme/Configure.cshtml @@ -58,7 +58,7 @@

            @T("Admin.Configuration.Themes.Validation.ErrorReportTitle")

            @parsingError
            - + @T("Admin.Configuration.Themes.Validation.RestorePrevValues")
            diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 249126c1b9..70bd28caf6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -1,8 +1,12 @@ @model TopicListModel + @using Telerik.Web.Mvc.UI + @{ - ViewBag.Title = T("Admin.ContentManagement.Topics").Text; + ViewBag.Title = T("Admin.ContentManagement.Topics").Text; + var gridPageSize = EngineContext.Current.Resolve().GridPageSize; } +
            @@ -16,29 +20,43 @@
            -@if (Model.AvailableStores.Count > 1) -{ - - - - - - - - - -
            - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) -
            - - -
            -} +
            + @if (Model.AvailableStores.Count > 1) + { +
            + @Html.SmartLabelFor(model => model.SearchStoreId) + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
            + } +
            + @Html.SmartLabelFor(model => model.SystemName) + @Html.TextBoxFor(model => Model.SystemName, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.Title) + @Html.TextBoxFor(model => Model.Title, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.WidgetZone) + @Html.TextBoxFor(model => Model.WidgetZone, new { @class = "form-control" }) +
            +
            + @Html.SmartLabelFor(model => model.RenderAsWidget) + @Html.EditorFor(model => Model.RenderAsWidget, new { @class = "form-control" }) +
            + +
            + @if (Model.AvailableStores.Count > 1) + { + + } + + +
            +
            +
            @(Html.Telerik().Grid() .Name("topics-grid") @@ -72,18 +90,17 @@ }) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) .ClientEvents(events => events.OnDataBinding("onDataBinding")) + .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() .EnableCustomBinding(true))
            - -

            - diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html deleted file mode 100644 index c2d82aa402..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/tmpFrameset.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css deleted file mode 100644 index 496d731250..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.css +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.html or http://ckeditor.com/license -*/ - -html, body -{ - background-color: transparent; - margin: 0px; - padding: 0px; -} - -body -{ - padding: 10px; -} - -body, td, input, select, textarea -{ - font-size: 11px; - font-family: 'Microsoft Sans Serif' , Arial, Helvetica, Verdana; -} - -.midtext -{ - padding:0px; - margin:10px; -} - -.midtext p -{ - padding:0px; - margin:10px; -} - -.Button -{ - border: #737357 1px solid; - color: #3b3b1f; - background-color: #c7c78f; -} - -.PopupTabArea -{ - color: #737357; - background-color: #e3e3c7; -} - -.PopupTitleBorder -{ - border-bottom: #d5d59d 1px solid; -} -.PopupTabEmptyArea -{ - padding-left: 10px; - border-bottom: #d5d59d 1px solid; -} - -.PopupTab, .PopupTabSelected -{ - border-right: #d5d59d 1px solid; - border-top: #d5d59d 1px solid; - border-left: #d5d59d 1px solid; - padding: 3px 5px 3px 5px; - color: #737357; -} - -.PopupTab -{ - margin-top: 1px; - border-bottom: #d5d59d 1px solid; - cursor: pointer; -} - -.PopupTabSelected -{ - font-weight: bold; - cursor: default; - padding-top: 4px; - border-bottom: #f1f1e3 1px solid; - background-color: #f1f1e3; -} diff --git a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js b/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js deleted file mode 100644 index b53a48cf47..0000000000 --- a/src/Presentation/SmartStore.Web/Content/editors/ckeditor/plugins/wsc/dialogs/wsc.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - For licensing, see LICENSE.html or http://ckeditor.com/license -*/ -(function(){function q(a){return a&&a.domId&&a.getInputElement().$?a.getInputElement():a&&a.$?a:!1}function z(a){if(!a)throw"Languages-by-groups list are required for construct selectbox";var c=[],d="",f;for(f in a)for(var g in a[f]){var h=a[f][g];"en_US"==h?d=h:c.push(h)}c.sort();d&&c.unshift(d);return{getCurrentLangGroup:function(c){a:{for(var d in a)for(var f in a[d])if(f.toUpperCase()===c.toUpperCase()){c=d;break a}c=""}return c},setLangList:function(){var c={},d;for(d in a)for(var f in a[d])c[a[d][f]]= -f;return c}()}}var e=function(){var a=function(a,b,f){var f=f||{},g=f.expires;if("number"==typeof g&&g){var h=new Date;h.setTime(h.getTime()+1E3*g);g=f.expires=h}g&&g.toUTCString&&(f.expires=g.toUTCString());var b=encodeURIComponent(b),a=a+"="+b,e;for(e in f)b=f[e],a+="; "+e,!0!==b&&(a+="="+b);document.cookie=a};return{postMessage:{init:function(a){window.addEventListener?window.addEventListener("message",a,!1):window.attachEvent("onmessage",a)},send:function(a){var b=Object.prototype.toString,f= -a.fn||null,g=a.id||"",e=a.target||window,i=a.message||{id:g};a.message&&"[object Object]"==b.call(a.message)&&(a.message.id||(a.message.id=g),i=a.message);a=window.JSON.stringify(i,f);e.postMessage(a,"*")},unbindHandler:function(a){window.removeEventListener?window.removeEventListener("message",a,!1):window.detachEvent("onmessage",a)}},hash:{create:function(){},parse:function(){}},cookie:{set:a,get:function(a){return(a=document.cookie.match(RegExp("(?:^|; )"+a.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, -"\\$1")+"=([^;]*)")))?decodeURIComponent(a[1]):void 0},remove:function(c){a(c,"",{expires:-1})}},misc:{findFocusable:function(a){var b=null;a&&(b=a.find("a[href], area[href], input, select, textarea, button, *[tabindex], *[contenteditable]"));return b},isVisible:function(a){return!(0===a.offsetWidth||0==a.offsetHeight||"none"===(document.defaultView&&document.defaultView.getComputedStyle?document.defaultView.getComputedStyle(a,null).display:a.currentStyle?a.currentStyle.display:a.style.display))}, -hasClass:function(a,b){return!(!a.className||!a.className.match(RegExp("(\\s|^)"+b+"(\\s|$)")))}}}}(),a=a||{};a.TextAreaNumber=null;a.load=!0;a.cmd={SpellTab:"spell",Thesaurus:"thes",GrammTab:"grammar"};a.dialog=null;a.optionNode=null;a.selectNode=null;a.grammerSuggest=null;a.textNode={};a.iframeMain=null;a.dataTemp="";a.div_overlay=null;a.textNodeInfo={};a.selectNode={};a.selectNodeResponce={};a.langList=null;a.langSelectbox=null;a.banner="";a.show_grammar=null;a.div_overlay_no_check=null;a.targetFromFrame= -{};a.onLoadOverlay=null;a.LocalizationComing={};a.OverlayPlace=null;a.LocalizationButton={ChangeTo:{instance:null,text:"Change to"},ChangeAll:{instance:null,text:"Change All"},IgnoreWord:{instance:null,text:"Ignore word"},IgnoreAllWords:{instance:null,text:"Ignore all words"},Options:{instance:null,text:"Options",optionsDialog:{instance:null}},AddWord:{instance:null,text:"Add word"},FinishChecking:{instance:null,text:"Finish Checking"}};a.LocalizationLabel={ChangeTo:{instance:null,text:"Change to"}, -Suggestions:{instance:null,text:"Suggestions"}};var A=function(b){var c,d;for(d in b)c=b[d].instance.getElement().getFirst()||b[d].instance.getElement(),c.setText(a.LocalizationComing[d])},B=function(b){for(var c in b){if(!b[c].instance.setLabel)break;b[c].instance.setLabel(a.LocalizationComing[c])}},j,r;a.framesetHtml=function(b){return"'}; -a.setIframe=function(b,c){var d;d=a.framesetHtml(c);var f=a.iframeNumber+"_"+c;b.getElement().setHtml(d);d=document.getElementById(f);d=d.contentWindow?d.contentWindow:d.contentDocument.document?d.contentDocument.document:d.contentDocument;d.document.open();d.document.write('iframe
            + + @property value + @type mixed + @default element's text + **/ + value: null, + /** + Callback to perform custom displaying of value in element's text. + If `null`, default input's display used. + If `false`, no displaying methods will be called, element's text will never change. + Runs under element's scope. + _**Parameters:**_ + + * `value` current value to be displayed + * `response` server response (if display called after ajax submit), since 1.4.0 + + For _inputs with source_ (select, checklist) parameters are different: + + * `value` current value to be displayed + * `sourceData` array of items for current input (e.g. dropdown items) + * `response` server response (if display called after ajax submit), since 1.4.0 + + To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`. + + @property display + @type function|boolean + @default null + @since 1.2.0 + @example + display: function(value, sourceData) { + //display checklist as comma-separated values + var html = [], + checked = $.fn.editableutils.itemsByValue(value, sourceData); + + if(checked.length) { + $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); }); + $(this).html(html.join(', ')); + } else { + $(this).empty(); + } + } + **/ + display: null, + /** + Css class applied when editable text is empty. + + @property emptyclass + @type string + @since 1.4.1 + @default editable-empty + **/ + emptyclass: 'editable-empty', + /** + Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`). + You may set it to `null` if you work with editables locally and submit them together. + + @property unsavedclass + @type string + @since 1.4.1 + @default editable-unsaved + **/ + unsavedclass: 'editable-unsaved', + /** + If selector is provided, editable will be delegated to the specified targets. + Usefull for dynamically generated DOM elements. + **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, + as they actually become editable only after first click. + You should manually set class `editable-click` to these elements. + Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element: + + @property selector + @type string + @since 1.4.1 + @default null + @example +
            + + Empty + + Operator +
            + + + **/ + selector: null, + /** + Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers. + + @property highlight + @type string|boolean + @since 1.4.5 + @default #FFFF80 + **/ + highlight: '#FFFF80' + }; + +}(window.jQuery)); + +/** +AbstractInput - base class for all editable inputs. +It defines interface to be implemented by any input type. +To create your own input you can inherit from this class. + +@class abstractinput +**/ +(function ($) { + "use strict"; + + //types + $.fn.editabletypes = {}; + + var AbstractInput = function () { }; + + AbstractInput.prototype = { + /** + Initializes input + + @method init() + **/ + init: function (type, options, defaults) { + this.type = type; + this.options = $.extend({}, defaults, options); + }, + + /* + this method called before render to init $tpl that is inserted in DOM + */ + prerender: function () { + this.$tpl = $(this.options.tpl); //whole tpl as jquery object + this.$input = this.$tpl; //control itself, can be changed in render method + this.$clear = null; //clear button + this.error = null; //error message, if input cannot be rendered + }, + + /** + Renders input from tpl. Can return jQuery deferred object. + Can be overwritten in child objects + + @method render() + **/ + render: function () { + + }, + + /** + Sets element's html by value. + + @method value2html(value, element) + @param {mixed} value + @param {DOMElement} element + **/ + value2html: function (value, element) { + $(element)[this.options.escape ? 'text' : 'html']($.trim(value)); + }, + + /** + Converts element's html to value + + @method html2value(html) + @param {string} html + @returns {mixed} + **/ + html2value: function (html) { + return $('
            ').html(html).text(); + }, + + /** + Converts value to string (for internal compare). For submitting to server used value2submit(). + + @method value2str(value) + @param {mixed} value + @returns {string} + **/ + value2str: function (value) { + return String(value); + }, + + /** + Converts string received from server into value. Usually from `data-value` attribute. + + @method str2value(str) + @param {string} str + @returns {mixed} + **/ + str2value: function (str) { + return str; + }, + + /** + Converts value for submitting to server. Result can be string or object. + + @method value2submit(value) + @param {mixed} value + @returns {mixed} + **/ + value2submit: function (value) { + return value; + }, + + /** + Sets value of input. + + @method value2input(value) + @param {mixed} value + **/ + value2input: function (value) { + this.$input.val(value); + }, + + /** + Returns value of input. Value can be object (e.g. datepicker) + + @method input2value() + **/ + input2value: function () { + return this.$input.val(); + }, + + /** + Activates input. For text it sets focus. + + @method activate() + **/ + activate: function () { + if (this.$input.is(':visible')) { + this.$input.focus(); + } + }, + + /** + Creates input. + + @method clear() + **/ + clear: function () { + this.$input.val(null); + }, + + /** + method to escape html. + **/ + escape: function (str) { + return $('
            ').text(str).html(); + }, + + /** + attach handler to automatically submit form when value changed (useful when buttons not shown) + **/ + autosubmit: function () { + + }, + + /** + Additional actions when destroying element + **/ + destroy: function () { + }, + + // -------- helper functions -------- + setClass: function () { + if (this.options.inputclass) { + this.$input.addClass(this.options.inputclass); + } + }, + + setAttr: function (attr) { + if (this.options[attr] !== undefined && this.options[attr] !== null) { + this.$input.attr(attr, this.options[attr]); + } + }, + + option: function (key, value) { + this.options[key] = value; + } + + }; + + AbstractInput.defaults = { + /** + HTML template of input. Normally you should not change it. + + @property tpl + @type string + @default '' + **/ + tpl: '', + /** + CSS class automatically applied to input + + @property inputclass + @type string + @default null + **/ + inputclass: null, + + /** + If `true` - html will be escaped in content of element via $.text() method. + If `false` - html will not be escaped, $.html() used. + When you use own `display` function, this option obviosly has no effect. + + @property escape + @type boolean + @since 1.5.0 + @default true + **/ + escape: true, + + //scope for external methods (e.g. source defined as function) + //for internal use only + scope: null, + + //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults) + showbuttons: true + }; + + $.extend($.fn.editabletypes, { abstractinput: AbstractInput }); + +}(window.jQuery)); + +/** +List - abstract class for inputs that have source option loaded from js array or via ajax + +@class list +@extends abstractinput +**/ +(function ($) { + "use strict"; + + var List = function (options) { + + }; + + $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); + + $.extend(List.prototype, { + render: function () { + var deferred = $.Deferred(); + + this.error = null; + this.onSourceReady(function () { + this.renderList(); + deferred.resolve(); + }, function () { + this.error = this.options.sourceError; + deferred.resolve(); + }); + + return deferred.promise(); + }, + + html2value: function (html) { + return null; //can't set value by text + }, + + value2html: function (value, element, display, response) { + var deferred = $.Deferred(), + success = function () { + if (typeof display === 'function') { + //custom display method + display.call(element, value, this.sourceData, response); + } else { + this.value2htmlFinal(value, element); + } + deferred.resolve(); + }; + + //for null value just call success without loading source + if (value === null) { + success.call(this); + } else { + this.onSourceReady(success, function () { deferred.resolve(); }); + } + + return deferred.promise(); + }, + + // ------------- additional functions ------------ + + onSourceReady: function (success, error) { + //run source if it function + var source; + if ($.isFunction(this.options.source)) { + source = this.options.source.call(this.options.scope); + this.sourceData = null; + //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed + } else { + source = this.options.source; + } + + //if allready loaded just call success + if (this.options.sourceCache && $.isArray(this.sourceData)) { + success.call(this); + return; + } + + //try parse json in single quotes (for double quotes jquery does automatically) + try { + source = $.fn.editableutils.tryParseJson(source, false); + } catch (e) { + error.call(this); + return; + } + + //loading from url + if (typeof source === 'string') { + //try to get sourceData from cache + if (this.options.sourceCache) { + var cacheID = source, + cache; + + if (!$(document).data(cacheID)) { + $(document).data(cacheID, {}); + } + cache = $(document).data(cacheID); + + //check for cached data + if (cache.loading === false && cache.sourceData) { //take source from cache + this.sourceData = cache.sourceData; + this.doPrepend(); + success.call(this); + return; + } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later + cache.callbacks.push($.proxy(function () { + this.sourceData = cache.sourceData; + this.doPrepend(); + success.call(this); + }, this)); + + //also collecting error callbacks + cache.err_callbacks.push($.proxy(error, this)); + return; + } else { //no cache yet, activate it + cache.loading = true; + cache.callbacks = []; + cache.err_callbacks = []; + } + } + + //ajaxOptions for source. Can be overwritten bt options.sourceOptions + var ajaxOptions = $.extend({ + url: source, + type: 'get', + cache: false, + dataType: 'json', + success: $.proxy(function (data) { + if (cache) { + cache.loading = false; + } + this.sourceData = this.makeArray(data); + if ($.isArray(this.sourceData)) { + if (cache) { + //store result in cache + cache.sourceData = this.sourceData; + //run success callbacks for other fields waiting for this source + $.each(cache.callbacks, function () { this.call(); }); + } + this.doPrepend(); + success.call(this); + } else { + error.call(this); + if (cache) { + //run error callbacks for other fields waiting for this source + $.each(cache.err_callbacks, function () { this.call(); }); + } + } + }, this), + error: $.proxy(function () { + error.call(this); + if (cache) { + cache.loading = false; + //run error callbacks for other fields + $.each(cache.err_callbacks, function () { this.call(); }); + } + }, this) + }, this.options.sourceOptions); + + //loading sourceData from server + $.ajax(ajaxOptions); + + } else { //options as json/array + this.sourceData = this.makeArray(source); + + if ($.isArray(this.sourceData)) { + this.doPrepend(); + success.call(this); + } else { + error.call(this); + } + } + }, + + doPrepend: function () { + if (this.options.prepend === null || this.options.prepend === undefined) { + return; + } + + if (!$.isArray(this.prependData)) { + //run prepend if it is function (once) + if ($.isFunction(this.options.prepend)) { + this.options.prepend = this.options.prepend.call(this.options.scope); + } + + //try parse json in single quotes + this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); + + //convert prepend from string to object + if (typeof this.options.prepend === 'string') { + this.options.prepend = { '': this.options.prepend }; + } + + this.prependData = this.makeArray(this.options.prepend); + } + + if ($.isArray(this.prependData) && $.isArray(this.sourceData)) { + this.sourceData = this.prependData.concat(this.sourceData); + } + }, + + /* + renders input list + */ + renderList: function () { + // this method should be overwritten in child class + }, + + /* + set element's html by value + */ + value2htmlFinal: function (value, element) { + // this method should be overwritten in child class + }, + + /** + * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}] + */ + makeArray: function (data) { + var count, obj, result = [], item, iterateItem; + if (!data || typeof data === 'string') { + return null; + } + + if ($.isArray(data)) { //array + /* + function to iterate inside item of array if item is object. + Caclulates count of keys in item and store in obj. + */ + iterateItem = function (k, v) { + obj = { value: k, text: v }; + if (count++ >= 2) { + return false;// exit from `each` if item has more than one key. + } + }; + + for (var i = 0; i < data.length; i++) { + item = data[i]; + if (typeof item === 'object') { + count = 0; //count of keys inside item + $.each(item, iterateItem); + //case: [{val1: 'text1'}, {val2: 'text2} ...] + if (count === 1) { + result.push(obj); + //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...] + } else if (count > 1) { + //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text') + if (item.children) { + item.children = this.makeArray(item.children); + } + result.push(item); + } + } else { + //case: ['text1', 'text2' ...] + result.push({ value: item, text: item }); + } + } + } else { //case: {val1: 'text1', val2: 'text2, ...} + $.each(data, function (k, v) { + result.push({ value: k, text: v }); + }); + } + return result; + }, + + option: function (key, value) { + this.options[key] = value; + if (key === 'source') { + this.sourceData = null; + } + if (key === 'prepend') { + this.prependData = null; + } + } + + }); + + List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + Source data for list. + If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]` + For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order. + + If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option. + + If **function**, it should return data in format above (since 1.4.0). + + Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only): + @example + [ + {text: "group1", children: [ + {value: 1, text: "text1"}, + {value: 2, text: "text2"} + ]}, + ... + ] + + + @property source + @type string | array | object | function + @default null + **/ + source: null, + /** + Data automatically prepended to the beginning of dropdown list. + + @property prepend + @type string | array | object | function + @default false + **/ + prepend: false, + /** + Error message when list cannot be loaded (e.g. ajax error) + + @property sourceError + @type string + @default Error when loading list + **/ + sourceError: 'Error when loading list', + /** + if `true` and source is **string url** - results will be cached for fields with the same source. + Usefull for editable column in grid to prevent extra requests. + + @property sourceCache + @type boolean + @default true + @since 1.2.0 + **/ + sourceCache: true, + /** + Additional ajax options to be used in $.ajax() when loading list from server. + Useful to send extra parameters or change request method. + + @property sourceOptions + @type object|function + @default null + @since 1.5.0 + @example + sourceOptions: { + data: {param: 123}, + type: 'post' + } + + **/ + sourceOptions: null + }); + + $.fn.editabletypes.list = List; + +}(window.jQuery)); + +/** +Text input + +@class text +@extends abstractinput +@final +@example +awesome + +**/ +(function ($) { + "use strict"; + + var Text = function (options) { + this.init('text', options, Text.defaults); + }; + + $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput); + + $.extend(Text.prototype, { + render: function () { + this.renderClear(); + this.setClass(); + this.setAttr('placeholder'); + }, + + activate: function () { + if (this.$input.is(':visible')) { + this.$input.focus(); + // if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"],[type="number"],[type="email"]')) { + if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"]')) { + $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length); + } + + if (this.toggleClear) { + this.toggleClear(); + } + } + }, + + //render clear button + renderClear: function () { + if (this.options.clear) { + this.$clear = $(''); + this.$input.after(this.$clear) + .css('padding-right', 24) + .keyup($.proxy(function (e) { + //arrows, enter, tab, etc + if (~$.inArray(e.keyCode, [40, 38, 9, 13, 27])) { + return; + } + + clearTimeout(this.t); + var that = this; + this.t = setTimeout(function () { + that.toggleClear(e); + }, 100); + + }, this)) + .parent().css('position', 'relative'); + + this.$clear.click($.proxy(this.clear, this)); + } + }, + + postrender: function () { + /* + //now `clear` is positioned via css + if(this.$clear) { + //can position clear button only here, when form is shown and height can be calculated +// var h = this.$input.outerHeight(true) || 20, + var h = this.$clear.parent().height(), + delta = (h - this.$clear.height()) / 2; + + //this.$clear.css({bottom: delta, right: delta}); + } + */ + }, + + //show / hide clear button + toggleClear: function (e) { + if (!this.$clear) { + return; + } + + var len = this.$input.val().length, + visible = this.$clear.is(':visible'); + + if (len && !visible) { + this.$clear.show(); + } + + if (!len && visible) { + this.$clear.hide(); + } + }, + + clear: function () { + this.$clear.hide(); + this.$input.val('').focus(); + } + }); + + Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + @property tpl + @default + **/ + tpl: '', + /** + Placeholder attribute of input. Shown when input is empty. + + @property placeholder + @type string + @default null + **/ + placeholder: null, + + /** + Whether to show `clear` button + + @property clear + @type boolean + @default true + **/ + clear: true + }); + + $.fn.editabletypes.text = Text; + +}(window.jQuery)); + +/** +Textarea input + +@class textarea +@extends abstractinput +@final +@example +awesome comment! + +**/ +(function ($) { + "use strict"; + + var Textarea = function (options) { + this.init('textarea', options, Textarea.defaults); + }; + + $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput); + + $.extend(Textarea.prototype, { + render: function () { + this.setClass(); + this.setAttr('placeholder'); + this.setAttr('rows'); + + //ctrl + enter + this.$input.keydown(function (e) { + if (e.ctrlKey && e.which === 13) { + $(this).closest('form').submit(); + } + }); + }, + + //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant! + /* + value2html: function(value, element) { + var html = '', lines; + if(value) { + lines = value.split("\n"); + for (var i = 0; i < lines.length; i++) { + lines[i] = $('
            ').text(lines[i]).html(); + } + html = lines.join('
            '); + } + $(element).html(html); + }, + + html2value: function(html) { + if(!html) { + return ''; + } + + var regex = new RegExp(String.fromCharCode(10), 'g'); + var lines = html.split(//i); + for (var i = 0; i < lines.length; i++) { + var text = $('
            ').html(lines[i]).text(); + + // Remove newline characters (\n) to avoid them being converted by value2html() method + // thus adding extra
            tags + text = text.replace(regex, ''); + + lines[i] = text; + } + return lines.join("\n"); + }, + */ + activate: function () { + $.fn.editabletypes.text.prototype.activate.call(this); + } + }); + + Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { + /** + @property tpl + @default + **/ + tpl: '', + /** + @property inputclass + @default input-large + **/ + inputclass: 'input-large', + /** + Placeholder attribute of input. Shown when input is empty. + + @property placeholder + @type string + @default null + **/ + placeholder: null, + /** + Number of rows in textarea + + @property rows + @type integer + @default 7 + **/ + rows: 7 + }); + + $.fn.editabletypes.textarea = Textarea; + +}(window.jQuery)); + +/** +Select (dropdown) + +@class select +@extends list +@final +@example + + +**/ +(function ($) { + "use strict"; + + var Select = function (options) { + this.init('select', options, Select.defaults); + }; + + $.fn.editableutils.inherit(Select, $.fn.editabletypes.list); + + $.extend(Select.prototype, { + renderList: function () { + this.$input.empty(); + var escape = this.options.escape; + + var fillItems = function ($el, data) { + var attr; + if ($.isArray(data)) { + for (var i = 0; i < data.length; i++) { + attr = {}; + if (data[i].children) { + attr.label = data[i].text; + $el.append(fillItems($('', attr), data[i].children)); + } else { + attr.value = data[i].value; + if (data[i].disabled) { + attr.disabled = true; + } + var $option = $('
            ","
            "],tr:[2,"","
            "],td:[3,"","
            "],col:[2,"","
            "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
            ","
            "]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function() -{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(var c=0,d=this.length;c1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]===""&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
            ").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/_references.js b/src/Presentation/SmartStore.Web/Scripts/_references.js index 7b7fc4501e..46979a4f64 100644 Binary files a/src/Presentation/SmartStore.Web/Scripts/_references.js and b/src/Presentation/SmartStore.Web/Scripts/_references.js differ diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.requestAnimationFrame.js b/src/Presentation/SmartStore.Web/Scripts/jquery.requestAnimationFrame.js deleted file mode 100644 index 041a0b8e4a..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.requestAnimationFrame.js +++ /dev/null @@ -1,43 +0,0 @@ -/* -* jquery.requestAnimationFrame -* https://github.com/gnarf37/jquery-requestAnimationFrame -* Requires jQuery 1.8+ -* -* Copyright (c) 2012 Corey Frang -* Licensed under the MIT license. -*/ - -(function ($) { - - // FireFox apparently doesn't like using this from a variable... - window.requestAnimationFrame = window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame; - - var animating; - - function raf() { - if (animating) { - window.requestAnimationFrame(raf); - jQuery.fx.tick(); - } - } - - if (window.requestAnimationFrame) { - - jQuery.fx.timer = function (timer) { - if (timer() && jQuery.timers.push(timer) && !animating) { - animating = true; - raf(); - } - }; - - jQuery.fx.stop = function () { - animating = false; - }; - - } - -} (jQuery)); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.scrollTo.js b/src/Presentation/SmartStore.Web/Scripts/jquery.scrollTo.js deleted file mode 100644 index 475b7297ec..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.scrollTo.js +++ /dev/null @@ -1,215 +0,0 @@ -/** - * jQuery.ScrollTo - * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com - * Dual licensed under MIT and GPL. - * Date: 5/25/2009 - * - * @projectDescription Easy element scrolling using jQuery. - * http://flesler.blogspot.com/2007/10/jqueryscrollto.html - * Works with jQuery +1.2.6. Tested on FF 2/3, IE 6/7/8, Opera 9.5/6, Safari 3, Chrome 1 on WinXP. - * - * @author Ariel Flesler - * @version 1.4.2 - * - * @id jQuery.scrollTo - * @id jQuery.fn.scrollTo - * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements. - * The different options for target are: - * - A number position (will be applied to all axes). - * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes - * - A jQuery/DOM element ( logically, child of the element to scroll ) - * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc ) - * - A hash { top:x, left:y }, x and y can be any kind of number/string like above. -* - A percentage of the container's dimension/s, for example: 50% to go to the middle. - * - The string 'max' for go-to-end. - * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead. - * @param {Object,Function} settings Optional set of settings or the onAfter callback. - * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'. - * @option {Number} duration The OVERALL length of the animation. - * @option {String} easing The easing method for the animation. - * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position. - * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }. - * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes. - * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends. - * @option {Function} onAfter Function to be called after the scrolling ends. - * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends. - * @return {jQuery} Returns the same jQuery object, for chaining. - * - * @desc Scroll to a fixed position - * @example $('div').scrollTo( 340 ); - * - * @desc Scroll relatively to the actual position - * @example $('div').scrollTo( '+=340px', { axis:'y' } ); - * - * @dec Scroll using a selector (relative to the scrolled element) - * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } ); - * - * @ Scroll to a DOM element (same for jQuery object) - * @example var second_child = document.getElementById('container').firstChild.nextSibling; - * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){ - * alert('scrolled!!'); - * }}); - * - * @desc Scroll on both axes, to different values - * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } ); - */ -;(function( $ ){ - - var $scrollTo = $.scrollTo = function( target, duration, settings ){ - $(window).scrollTo( target, duration, settings ); - }; - - $scrollTo.defaults = { - axis:'xy', - duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1 - }; - - // Returns the element that needs to be animated to scroll the window. - // Kept for backwards compatibility (specially for localScroll & serialScroll) - $scrollTo.window = function( scope ){ - return $(window)._scrollable(); - }; - - // Hack, hack, hack :) - // Returns the real elements to scroll (supports window/iframes, documents and regular nodes) - $.fn._scrollable = function(){ - return this.map(function(){ - var elem = this, - isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1; - - if( !isWin ) - return elem; - - var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem; - - return /webkit/.test(navigator.userAgent.toLowerCase()) || doc.compatMode == 'BackCompat' ? - doc.body : - doc.documentElement; - }); - }; - - $.fn.scrollTo = function( target, duration, settings ){ - if( typeof duration == 'object' ){ - settings = duration; - duration = 0; - } - if( typeof settings == 'function' ) - settings = { onAfter:settings }; - - if( target == 'max' ) - target = 9e9; - - settings = $.extend( {}, $scrollTo.defaults, settings ); - // Speed is still recognized for backwards compatibility - duration = duration || settings.speed || settings.duration; - // Make sure the settings are given right - settings.queue = settings.queue && settings.axis.length > 1; - - if( settings.queue ) - // Let's keep the overall duration - duration /= 2; - settings.offset = both( settings.offset ); - settings.over = both( settings.over ); - - return this._scrollable().each(function(){ - var elem = this, - $elem = $(elem), - targ = target, toff, attr = {}, - win = $elem.is('html,body'); - - switch( typeof targ ){ - // A number will pass the regex - case 'number': - case 'string': - if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){ - targ = both( targ ); - // We are done - break; - } - // Relative selector, no break! - targ = $(targ,this); - case 'object': - // DOMElement / jQuery - if( targ.is || targ.style ) - // Get the real position of the target - toff = (targ = $(targ)).offset(); - } - $.each( settings.axis.split(''), function( i, axis ){ - var Pos = axis == 'x' ? 'Left' : 'Top', - pos = Pos.toLowerCase(), - key = 'scroll' + Pos, - old = elem[key], - max = $scrollTo.max(elem, axis); - - if( toff ){// jQuery / DOMElement - attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] ); - - // If it's a dom element, reduce the margin - if( settings.margin ){ - attr[key] -= parseInt(targ.css('margin'+Pos)) || 0; - attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0; - } - - attr[key] += settings.offset[pos] || 0; - - if( settings.over[pos] ) - // Scroll to a fraction of its width/height - attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos]; - }else{ - var val = targ[pos]; - // Handle percentage values - attr[key] = val.slice && val.slice(-1) == '%' ? - parseFloat(val) / 100 * max - : val; - } - - // Number or 'number' - if( /^\d+$/.test(attr[key]) ) - // Check the limits - attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max ); - - // Queueing axes - if( !i && settings.queue ){ - // Don't waste time animating, if there's no need. - if( old != attr[key] ) - // Intermediate animation - animate( settings.onAfterFirst ); - // Don't animate this axis again in the next iteration. - delete attr[key]; - } - }); - - animate( settings.onAfter ); - - function animate( callback ){ - $elem.animate( attr, duration, settings.easing, callback && function(){ - callback.call(this, target, settings); - }); - }; - - }).end(); - }; - - // Max scrolling position, works on quirks mode - // It only fails (not too badly) on IE, quirks mode. - $scrollTo.max = function( elem, axis ){ - var Dim = axis == 'x' ? 'Width' : 'Height', - scroll = 'scroll'+Dim; - - if( !$(elem).is('html,body') ) - return elem[scroll] - $(elem)[Dim.toLowerCase()](); - - var size = 'client' + Dim, - html = elem.ownerDocument.documentElement, - body = elem.ownerDocument.body; - - return Math.max( html[scroll], body[scroll] ) - - Math.min( html[size] , body[size] ); - - }; - - function both( val ){ - return typeof val == 'object' ? val : { top:val, left:val }; - }; - -})( jQuery ); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.serialScroll.js b/src/Presentation/SmartStore.Web/Scripts/jquery.serialScroll.js deleted file mode 100644 index 4fcdea2fb5..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.serialScroll.js +++ /dev/null @@ -1,222 +0,0 @@ -/*! - * jQuery.SerialScroll - * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com - * Dual licensed under MIT and GPL. - * Date: 06/14/2009 - * - * @projectDescription Animated scrolling of series. - * @author Ariel Flesler - * @version 1.2.2 - * - * @id jQuery.serialScroll - * @id jQuery.fn.serialScroll - * @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required. - * @return {jQuery} Returns the same jQuery object, for chaining. - * - * @link {http://flesler.blogspot.com/2008/02/jqueryserialscroll.html Homepage} - * - * Notes: - * - The plugin requires jQuery.ScrollTo. - * - The hash of settings, is passed to jQuery.ScrollTo, so its settings can be used as well. - */ -;(function( $ ){ - - var $serialScroll = $.serialScroll = function( settings ){ - return $(window).serialScroll( settings ); - }; - - // Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option. - // @link {http://demos.flesler.com/jquery/scrollTo/ ScrollTo's Demo} - $serialScroll.defaults = {// the defaults are public and can be overriden. - duration:1000, // how long to animate. - axis:'x', // which of top and left should be scrolled - event:'click', // on which event to react. - start:0, // first element (zero-based index) - step:1, // how many elements to scroll on each action - lock:true,// ignore events if already animating - cycle:true, // cycle endlessly ( constant velocity ) - constant:true, // use contant speed ? - smartJump:true - /* - navigation:null,// if specified, it's a selector a collection of items to navigate the container - target:window, // if specified, it's a selector to the element to be scrolled. - interval:0, // it's the number of milliseconds to automatically go to the next - lazy:false,// go find the elements each time (allows AJAX or JS content, or reordering) - stop:false, // stop any previous animations to avoid queueing - force:false,// force the scroll to the first element on start ? - jump: false,// if true, when the event is triggered on an element, the pane scrolls to it - items:null, // selector to the items (relative to the matched elements) - prev:null, // selector to the 'prev' button - next:null, // selector to the 'next' button - onBefore: function(){}, // function called before scrolling, if it returns false, the event is ignored - exclude:0 // exclude the last x elements, so we cannot scroll past the end - */ - }; - - $.fn.serialScroll = function( options ){ - - return this.each(function(){ - var - settings = $.extend( {}, $serialScroll.defaults, options ), - event = settings.event, // this one is just to get shorter code when compressed - step = settings.step, // ditto - lazy = settings.lazy, // ditto - context = settings.target ? this : document, // if a target is specified, then everything's relative to 'this'. - $pane = $(settings.target || this, context),// the element to be scrolled (will carry all the events) - pane = $pane[0], // will be reused, save it into a variable - items = settings.items, // will hold a lazy list of elements - active = settings.start, // active index - auto = settings.interval, // boolean, do auto or not - nav = settings.navigation, // save it now to make the code shorter - timer; // holds the interval id - - if( !lazy )// if not lazy, save the items now - items = getItems(); - - if( settings.force ) - jump( {}, active );// generate an initial call - - // Button binding, optional - $(settings.prev||[], context).bind( event, -step, move ); - $(settings.next||[], context).bind( event, step, move ); - - // Custom events bound to the container - if( !pane.ssbound )// don't bind more than once - $pane - .bind('prev.serialScroll', -step, move ) // you can trigger with just 'prev' - .bind('next.serialScroll', step, move ) // f.e: $(container).trigger('next'); - .bind('goto.serialScroll', jump ); // f.e: $(container).trigger('goto', 4 ); - - if( auto ) - $pane - .bind('start.serialScroll', function(e){ - if( !auto ){ - clear(); - auto = true; - next(); - } - }) - .bind('stop.serialScroll', function(){// stop a current animation - clear(); - auto = false; - }); - - $pane.bind('notify.serialScroll', function(e, elem){// let serialScroll know that the index changed externally - var i = index(elem); - if( i > -1 ) - active = i; - }); - - pane.ssbound = true;// avoid many bindings - - if( settings.jump )// can't use jump if using lazy items and a non-bubbling event - (lazy ? $pane : getItems()).bind( event, function( e ){ - jump( e, index(e.target) ); - }); - - /* - //BEGIN: original - if( nav ) - nav = $(nav, context).bind(event, function( e ){ - e.data = Math.round(getItems().length / nav.length) * nav.index(this); - jump( e, this ); - }); - //END: original - */ - - //BEGIN: altered code - if( nav ){ - var s = jQuery.type(nav) == "string" ? nav : nav.selector ; - - nav_n = s.split(','); - - for(var i=0, l=nav_n.length; i").html(data).contents().each(function () { - update.insertBefore(this, top); - }); - break; - case "AFTER": - $("
            ").html(data).contents().each(function () { - update.appendChild(this); - }); - break; - case "REPLACE-WITH": - $(update).replaceWith(data); - break; - default: - $(update).html(data); - break; - } - }); - } - - function asyncRequest(element, options) { - var confirm, loading, method, duration; - - confirm = element.getAttribute("data-ajax-confirm"); - if (confirm && !window.confirm(confirm)) { - return; - } - - loading = $(element.getAttribute("data-ajax-loading")); - duration = parseInt(element.getAttribute("data-ajax-loading-duration"), 10) || 0; - - $.extend(options, { - type: element.getAttribute("data-ajax-method") || undefined, - url: element.getAttribute("data-ajax-url") || undefined, - cache: !!element.getAttribute("data-ajax-cache"), - beforeSend: function (xhr) { - var result; - asyncOnBeforeSend(xhr, method); - result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments); - if (result !== false) { - loading.show(duration); - } - return result; - }, - complete: function () { - loading.hide(duration); - getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, arguments); - }, - success: function (data, status, xhr) { - asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html"); - getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(element, arguments); - }, - error: function () { - getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]).apply(element, arguments); - } - }); - - options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); - - method = options.type.toUpperCase(); - if (!isMethodProxySafe(method)) { - options.type = "POST"; - options.data.push({ name: "X-HTTP-Method-Override", value: method }); - } - - $.ajax(options); - } - - function validate(form) { - var validationInfo = $(form).data(data_validation); - return !validationInfo || !validationInfo.validate || validationInfo.validate(); - } - - $(document).on("click", "a[data-ajax=true]", function (evt) { - evt.preventDefault(); - asyncRequest(this, { - url: this.href, - type: "GET", - data: [] - }); - }); - - $(document).on("click", "form[data-ajax=true] input[type=image]", function (evt) { - var name = evt.target.name, - target = $(evt.target), - form = $(target.parents("form")[0]), - offset = target.offset(); - - form.data(data_click, [ - { name: name + ".x", value: Math.round(evt.pageX - offset.left) }, - { name: name + ".y", value: Math.round(evt.pageY - offset.top) } - ]); - - setTimeout(function () { - form.removeData(data_click); - }, 0); - }); - - $(document).on("click", "form[data-ajax=true] :submit", function (evt) { - var name = evt.currentTarget.name, - target = $(evt.target), - form = $(target.parents("form")[0]); - - form.data(data_click, name ? [{ name: name, value: evt.currentTarget.value }] : []); - form.data(data_target, target); - - setTimeout(function () { - form.removeData(data_click); - form.removeData(data_target); - }, 0); - }); - - $(document).on("submit", "form[data-ajax=true]", function (evt) { - var clickInfo = $(this).data(data_click) || [], - clickTarget = $(this).data(data_target), - isCancel = clickTarget && clickTarget.hasClass("cancel"); - evt.preventDefault(); - if (!isCancel && !validate(this)) { - return; - } - asyncRequest(this, { - url: this.action, - type: this.method || "GET", - data: clickInfo.concat($(this).serializeArray()) - }); - }); -}(jQuery)); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.unobtrusive-ajax.min.js b/src/Presentation/SmartStore.Web/Scripts/jquery.unobtrusive-ajax.min.js deleted file mode 100644 index 79c70571a7..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.unobtrusive-ajax.min.js +++ /dev/null @@ -1,19 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/* -** Unobtrusive Ajax support library for jQuery -** Copyright (C) Microsoft Corporation. All rights reserved. -*/ -(function(a){var b="unobtrusiveAjaxClick",d="unobtrusiveAjaxClickTarget",h="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function e(a){return a==="GET"||a==="POST"}function g(b,a){!e(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function i(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("
            ").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("
            ").html(b).contents().each(function(){c.appendChild(this)});break;case"REPLACE-WITH":a(c).replaceWith(b);break;default:a(c).html(b)}})}function f(b,d){var j,k,f,h;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));h=parseInt(b.getAttribute("data-ajax-loading-duration"),10)||0;a.extend(d,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,cache:!!b.getAttribute("data-ajax-cache"),beforeSend:function(d){var a;g(d,f);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(b,arguments);a!==false&&k.show(h);return a},complete:function(){k.hide(h);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(b,arguments)},success:function(a,e,d){i(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(b,arguments)},error:function(){c(b.getAttribute("data-ajax-failure"),["xhr","status","error"]).apply(b,arguments)}});d.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});f=d.type.toUpperCase();if(!e(f)){d.type="POST";d.data.push({name:"X-HTTP-Method-Override",value:f})}a.ajax(d)}function j(c){var b=a(c).data(h);return!b||!b.validate||b.validate()}a(document).on("click","a[data-ajax=true]",function(a){a.preventDefault();f(this,{url:this.href,type:"GET",data:[]})});a(document).on("click","form[data-ajax=true] input[type=image]",function(c){var g=c.target.name,e=a(c.target),f=a(e.parents("form")[0]),d=e.offset();f.data(b,[{name:g+".x",value:Math.round(c.pageX-d.left)},{name:g+".y",value:Math.round(c.pageY-d.top)}]);setTimeout(function(){f.removeData(b)},0)});a(document).on("click","form[data-ajax=true] :submit",function(e){var g=e.currentTarget.name,f=a(e.target),c=a(f.parents("form")[0]);c.data(b,g?[{name:g,value:e.currentTarget.value}]:[]);c.data(d,f);setTimeout(function(){c.removeData(b);c.removeData(d)},0)});a(document).on("submit","form[data-ajax=true]",function(h){var e=a(this).data(b)||[],c=a(this).data(d),g=c&&c.hasClass("cancel");h.preventDefault();if(!g&&!j(this))return;f(this,{url:this.action,type:this.method||"GET",data:e.concat(a(this).serializeArray())})})})(jQuery); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.js b/src/Presentation/SmartStore.Web/Scripts/jquery.validate.js deleted file mode 100644 index 6d39fab91c..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.js +++ /dev/null @@ -1,1574 +0,0 @@ -/*! - * jQuery Validation Plugin v1.16.0 - * - * http://jqueryvalidation.org/ - * - * Copyright (c) 2016 Jörn Zaefferer - * Released under the MIT license - */ -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - define( ["jquery"], factory ); - } else if (typeof module === "object" && module.exports) { - module.exports = factory( require( "jquery" ) ); - } else { - factory( jQuery ); - } -}(function( $ ) { - -$.extend( $.fn, { - - // http://jqueryvalidation.org/validate/ - validate: function( options ) { - - // If nothing is selected, return nothing; can't chain anyway - if ( !this.length ) { - if ( options && options.debug && window.console ) { - console.warn( "Nothing selected, can't validate, returning nothing." ); - } - return; - } - - // Check if a validator for this form was already created - var validator = $.data( this[ 0 ], "validator" ); - if ( validator ) { - return validator; - } - - // Add novalidate tag if HTML5. - this.attr( "novalidate", "novalidate" ); - - validator = new $.validator( options, this[ 0 ] ); - $.data( this[ 0 ], "validator", validator ); - - if ( validator.settings.onsubmit ) { - - this.on( "click.validate", ":submit", function( event ) { - if ( validator.settings.submitHandler ) { - validator.submitButton = event.target; - } - - // Allow suppressing validation by adding a cancel class to the submit button - if ( $( this ).hasClass( "cancel" ) ) { - validator.cancelSubmit = true; - } - - // Allow suppressing validation by adding the html5 formnovalidate attribute to the submit button - if ( $( this ).attr( "formnovalidate" ) !== undefined ) { - validator.cancelSubmit = true; - } - } ); - - // Validate the form on submit - this.on( "submit.validate", function( event ) { - if ( validator.settings.debug ) { - - // Prevent form submit to be able to see console output - event.preventDefault(); - } - function handle() { - var hidden, result; - if ( validator.settings.submitHandler ) { - if ( validator.submitButton ) { - - // Insert a hidden input as a replacement for the missing submit button - hidden = $( "" ) - .attr( "name", validator.submitButton.name ) - .val( $( validator.submitButton ).val() ) - .appendTo( validator.currentForm ); - } - result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); - if ( validator.submitButton ) { - - // And clean up afterwards; thanks to no-block-scope, hidden can be referenced - hidden.remove(); - } - if ( result !== undefined ) { - return result; - } - return false; - } - return true; - } - - // Prevent submit for invalid forms or custom submit handlers - if ( validator.cancelSubmit ) { - validator.cancelSubmit = false; - return handle(); - } - if ( validator.form() ) { - if ( validator.pendingRequest ) { - validator.formSubmitted = true; - return false; - } - return handle(); - } else { - validator.focusInvalid(); - return false; - } - } ); - } - - return validator; - }, - - // http://jqueryvalidation.org/valid/ - valid: function() { - var valid, validator, errorList; - - if ( $( this[ 0 ] ).is( "form" ) ) { - valid = this.validate().form(); - } else { - errorList = []; - valid = true; - validator = $( this[ 0 ].form ).validate(); - this.each( function() { - valid = validator.element( this ) && valid; - if ( !valid ) { - errorList = errorList.concat( validator.errorList ); - } - } ); - validator.errorList = errorList; - } - return valid; - }, - - // http://jqueryvalidation.org/rules/ - rules: function( command, argument ) { - var element = this[ 0 ], - settings, staticRules, existingRules, data, param, filtered; - - // If nothing is selected, return empty object; can't chain anyway - if ( element == null || element.form == null ) { - return; - } - - if ( command ) { - settings = $.data( element.form, "validator" ).settings; - staticRules = settings.rules; - existingRules = $.validator.staticRules( element ); - switch ( command ) { - case "add": - $.extend( existingRules, $.validator.normalizeRule( argument ) ); - - // Remove messages from rules, but allow them to be set separately - delete existingRules.messages; - staticRules[ element.name ] = existingRules; - if ( argument.messages ) { - settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages ); - } - break; - case "remove": - if ( !argument ) { - delete staticRules[ element.name ]; - return existingRules; - } - filtered = {}; - $.each( argument.split( /\s/ ), function( index, method ) { - filtered[ method ] = existingRules[ method ]; - delete existingRules[ method ]; - if ( method === "required" ) { - $( element ).removeAttr( "aria-required" ); - } - } ); - return filtered; - } - } - - data = $.validator.normalizeRules( - $.extend( - {}, - $.validator.classRules( element ), - $.validator.attributeRules( element ), - $.validator.dataRules( element ), - $.validator.staticRules( element ) - ), element ); - - // Make sure required is at front - if ( data.required ) { - param = data.required; - delete data.required; - data = $.extend( { required: param }, data ); - $( element ).attr( "aria-required", "true" ); - } - - // Make sure remote is at back - if ( data.remote ) { - param = data.remote; - delete data.remote; - data = $.extend( data, { remote: param } ); - } - - return data; - } -} ); - -// Custom selectors -$.extend( $.expr.pseudos || $.expr[ ":" ], { // '|| $.expr[ ":" ]' here enables backwards compatibility to jQuery 1.7. Can be removed when dropping jQ 1.7.x support - - // http://jqueryvalidation.org/blank-selector/ - blank: function( a ) { - return !$.trim( "" + $( a ).val() ); - }, - - // http://jqueryvalidation.org/filled-selector/ - filled: function( a ) { - var val = $( a ).val(); - return val !== null && !!$.trim( "" + val ); - }, - - // http://jqueryvalidation.org/unchecked-selector/ - unchecked: function( a ) { - return !$( a ).prop( "checked" ); - } -} ); - -// Constructor for validator -$.validator = function( options, form ) { - this.settings = $.extend( true, {}, $.validator.defaults, options ); - this.currentForm = form; - this.init(); -}; - -// http://jqueryvalidation.org/jQuery.validator.format/ -$.validator.format = function( source, params ) { - if ( arguments.length === 1 ) { - return function() { - var args = $.makeArray( arguments ); - args.unshift( source ); - return $.validator.format.apply( this, args ); - }; - } - if ( params === undefined ) { - return source; - } - if ( arguments.length > 2 && params.constructor !== Array ) { - params = $.makeArray( arguments ).slice( 1 ); - } - if ( params.constructor !== Array ) { - params = [ params ]; - } - $.each( params, function( i, n ) { - source = source.replace( new RegExp( "\\{" + i + "\\}", "g" ), function() { - return n; - } ); - } ); - return source; -}; - -$.extend( $.validator, { - - defaults: { - messages: {}, - groups: {}, - rules: {}, - errorClass: "error", - pendingClass: "pending", - validClass: "valid", - errorElement: "label", - focusCleanup: false, - focusInvalid: true, - errorContainer: $( [] ), - errorLabelContainer: $( [] ), - onsubmit: true, - ignore: ":hidden", - ignoreTitle: false, - onfocusin: function( element ) { - this.lastActive = element; - - // Hide error label and remove error class on focus if enabled - if ( this.settings.focusCleanup ) { - if ( this.settings.unhighlight ) { - this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); - } - this.hideThese( this.errorsFor( element ) ); - } - }, - onfocusout: function( element ) { - if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { - this.element( element ); - } - }, - onkeyup: function( element, event ) { - - // Avoid revalidate the field when pressing one of the following keys - // Shift => 16 - // Ctrl => 17 - // Alt => 18 - // Caps lock => 20 - // End => 35 - // Home => 36 - // Left arrow => 37 - // Up arrow => 38 - // Right arrow => 39 - // Down arrow => 40 - // Insert => 45 - // Num lock => 144 - // AltGr key => 225 - var excludedKeys = [ - 16, 17, 18, 20, 35, 36, 37, - 38, 39, 40, 45, 144, 225 - ]; - - if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { - return; - } else if ( element.name in this.submitted || element.name in this.invalid ) { - this.element( element ); - } - }, - onclick: function( element ) { - - // Click on selects, radiobuttons and checkboxes - if ( element.name in this.submitted ) { - this.element( element ); - - // Or option elements, check parent select in that case - } else if ( element.parentNode.name in this.submitted ) { - this.element( element.parentNode ); - } - }, - highlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); - } else { - $( element ).addClass( errorClass ).removeClass( validClass ); - } - }, - unhighlight: function( element, errorClass, validClass ) { - if ( element.type === "radio" ) { - this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); - } else { - $( element ).removeClass( errorClass ).addClass( validClass ); - } - } - }, - - // http://jqueryvalidation.org/jQuery.validator.setDefaults/ - setDefaults: function( settings ) { - $.extend( $.validator.defaults, settings ); - }, - - messages: { - required: "This field is required.", - remote: "Please fix this field.", - email: "Please enter a valid email address.", - url: "Please enter a valid URL.", - date: "Please enter a valid date.", - dateISO: "Please enter a valid date (ISO).", - number: "Please enter a valid number.", - digits: "Please enter only digits.", - equalTo: "Please enter the same value again.", - maxlength: $.validator.format( "Please enter no more than {0} characters." ), - minlength: $.validator.format( "Please enter at least {0} characters." ), - rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ), - range: $.validator.format( "Please enter a value between {0} and {1}." ), - max: $.validator.format( "Please enter a value less than or equal to {0}." ), - min: $.validator.format( "Please enter a value greater than or equal to {0}." ), - step: $.validator.format( "Please enter a multiple of {0}." ) - }, - - autoCreateRanges: false, - - prototype: { - - init: function() { - this.labelContainer = $( this.settings.errorLabelContainer ); - this.errorContext = this.labelContainer.length && this.labelContainer || $( this.currentForm ); - this.containers = $( this.settings.errorContainer ).add( this.settings.errorLabelContainer ); - this.submitted = {}; - this.valueCache = {}; - this.pendingRequest = 0; - this.pending = {}; - this.invalid = {}; - this.reset(); - - var groups = ( this.groups = {} ), - rules; - $.each( this.settings.groups, function( key, value ) { - if ( typeof value === "string" ) { - value = value.split( /\s/ ); - } - $.each( value, function( index, name ) { - groups[ name ] = key; - } ); - } ); - rules = this.settings.rules; - $.each( rules, function( key, value ) { - rules[ key ] = $.validator.normalizeRule( value ); - } ); - - function delegate( event ) { - - // Set form expando on contenteditable - if ( !this.form && this.hasAttribute( "contenteditable" ) ) { - this.form = $( this ).closest( "form" )[ 0 ]; - } - - var validator = $.data( this.form, "validator" ), - eventType = "on" + event.type.replace( /^validate/, "" ), - settings = validator.settings; - if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { - settings[ eventType ].call( validator, this, event ); - } - } - - $( this.currentForm ) - .on( "focusin.validate focusout.validate keyup.validate", - ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + - "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + - "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + - "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate ) - - // Support: Chrome, oldIE - // "select" is provided as event.target when clicking a option - .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate ); - - if ( this.settings.invalidHandler ) { - $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); - } - - // Add aria-required to any Static/Data/Class required fields before first validation - // Screen readers require this attribute to be present before the initial submission http://www.w3.org/TR/WCAG-TECHS/ARIA2.html - $( this.currentForm ).find( "[required], [data-rule-required], .required" ).attr( "aria-required", "true" ); - }, - - // http://jqueryvalidation.org/Validator.form/ - form: function() { - this.checkForm(); - $.extend( this.submitted, this.errorMap ); - this.invalid = $.extend( {}, this.errorMap ); - if ( !this.valid() ) { - $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); - } - this.showErrors(); - return this.valid(); - }, - - checkForm: function() { - this.prepareForm(); - for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) { - this.check( elements[ i ] ); - } - return this.valid(); - }, - - // http://jqueryvalidation.org/Validator.element/ - element: function( element ) { - var cleanElement = this.clean( element ), - checkElement = this.validationTargetFor( cleanElement ), - v = this, - result = true, - rs, group; - - if ( checkElement === undefined ) { - delete this.invalid[ cleanElement.name ]; - } else { - this.prepareElement( checkElement ); - this.currentElements = $( checkElement ); - - // If this element is grouped, then validate all group elements already - // containing a value - group = this.groups[ checkElement.name ]; - if ( group ) { - $.each( this.groups, function( name, testgroup ) { - if ( testgroup === group && name !== checkElement.name ) { - cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) ); - if ( cleanElement && cleanElement.name in v.invalid ) { - v.currentElements.push( cleanElement ); - result = v.check( cleanElement ) && result; - } - } - } ); - } - - rs = this.check( checkElement ) !== false; - result = result && rs; - if ( rs ) { - this.invalid[ checkElement.name ] = false; - } else { - this.invalid[ checkElement.name ] = true; - } - - if ( !this.numberOfInvalids() ) { - - // Hide error containers on last error - this.toHide = this.toHide.add( this.containers ); - } - this.showErrors(); - - // Add aria-invalid status for screen readers - $( element ).attr( "aria-invalid", !rs ); - } - - return result; - }, - - // http://jqueryvalidation.org/Validator.showErrors/ - showErrors: function( errors ) { - if ( errors ) { - var validator = this; - - // Add items to error list and map - $.extend( this.errorMap, errors ); - this.errorList = $.map( this.errorMap, function( message, name ) { - return { - message: message, - element: validator.findByName( name )[ 0 ] - }; - } ); - - // Remove items from success list - this.successList = $.grep( this.successList, function( element ) { - return !( element.name in errors ); - } ); - } - if ( this.settings.showErrors ) { - this.settings.showErrors.call( this, this.errorMap, this.errorList ); - } else { - this.defaultShowErrors(); - } - }, - - // http://jqueryvalidation.org/Validator.resetForm/ - resetForm: function() { - if ( $.fn.resetForm ) { - $( this.currentForm ).resetForm(); - } - this.invalid = {}; - this.submitted = {}; - this.prepareForm(); - this.hideErrors(); - var elements = this.elements() - .removeData( "previousValue" ) - .removeAttr( "aria-invalid" ); - - this.resetElements( elements ); - }, - - resetElements: function( elements ) { - var i; - - if ( this.settings.unhighlight ) { - for ( i = 0; elements[ i ]; i++ ) { - this.settings.unhighlight.call( this, elements[ i ], - this.settings.errorClass, "" ); - this.findByName( elements[ i ].name ).removeClass( this.settings.validClass ); - } - } else { - elements - .removeClass( this.settings.errorClass ) - .removeClass( this.settings.validClass ); - } - }, - - numberOfInvalids: function() { - return this.objectLength( this.invalid ); - }, - - objectLength: function( obj ) { - /* jshint unused: false */ - var count = 0, - i; - for ( i in obj ) { - if ( obj[ i ] ) { - count++; - } - } - return count; - }, - - hideErrors: function() { - this.hideThese( this.toHide ); - }, - - hideThese: function( errors ) { - errors.not( this.containers ).text( "" ); - this.addWrapper( errors ).hide(); - }, - - valid: function() { - return this.size() === 0; - }, - - size: function() { - return this.errorList.length; - }, - - focusInvalid: function() { - if ( this.settings.focusInvalid ) { - try { - $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || [] ) - .filter( ":visible" ) - .focus() - - // Manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find - .trigger( "focusin" ); - } catch ( e ) { - - // Ignore IE throwing errors when focusing hidden elements - } - } - }, - - findLastActive: function() { - var lastActive = this.lastActive; - return lastActive && $.grep( this.errorList, function( n ) { - return n.element.name === lastActive.name; - } ).length === 1 && lastActive; - }, - - elements: function() { - var validator = this, - rulesCache = {}; - - // Select all valid inputs inside the form (no submit or reset buttons) - return $( this.currentForm ) - .find( "input, select, textarea, [contenteditable]" ) - .not( ":submit, :reset, :image, :disabled" ) - .not( this.settings.ignore ) - .filter( function() { - var name = this.name || $( this ).attr( "name" ); // For contenteditable - if ( !name && validator.settings.debug && window.console ) { - console.error( "%o has no name assigned", this ); - } - - // Set form expando on contenteditable - if ( this.hasAttribute( "contenteditable" ) ) { - this.form = $( this ).closest( "form" )[ 0 ]; - } - - // Select only the first element for each name, and only those with rules specified - if ( name in rulesCache || !validator.objectLength( $( this ).rules() ) ) { - return false; - } - - rulesCache[ name ] = true; - return true; - } ); - }, - - clean: function( selector ) { - return $( selector )[ 0 ]; - }, - - errors: function() { - var errorClass = this.settings.errorClass.split( " " ).join( "." ); - return $( this.settings.errorElement + "." + errorClass, this.errorContext ); - }, - - resetInternals: function() { - this.successList = []; - this.errorList = []; - this.errorMap = {}; - this.toShow = $( [] ); - this.toHide = $( [] ); - }, - - reset: function() { - this.resetInternals(); - this.currentElements = $( [] ); - }, - - prepareForm: function() { - this.reset(); - this.toHide = this.errors().add( this.containers ); - }, - - prepareElement: function( element ) { - this.reset(); - this.toHide = this.errorsFor( element ); - }, - - elementValue: function( element ) { - var $element = $( element ), - type = element.type, - val, idx; - - if ( type === "radio" || type === "checkbox" ) { - return this.findByName( element.name ).filter( ":checked" ).val(); - } else if ( type === "number" && typeof element.validity !== "undefined" ) { - return element.validity.badInput ? "NaN" : $element.val(); - } - - if ( element.hasAttribute( "contenteditable" ) ) { - val = $element.text(); - } else { - val = $element.val(); - } - - if ( type === "file" ) { - - // Modern browser (chrome & safari) - if ( val.substr( 0, 12 ) === "C:\\fakepath\\" ) { - return val.substr( 12 ); - } - - // Legacy browsers - // Unix-based path - idx = val.lastIndexOf( "/" ); - if ( idx >= 0 ) { - return val.substr( idx + 1 ); - } - - // Windows-based path - idx = val.lastIndexOf( "\\" ); - if ( idx >= 0 ) { - return val.substr( idx + 1 ); - } - - // Just the file name - return val; - } - - if ( typeof val === "string" ) { - return val.replace( /\r/g, "" ); - } - return val; - }, - - check: function( element ) { - element = this.validationTargetFor( this.clean( element ) ); - - var rules = $( element ).rules(), - rulesCount = $.map( rules, function( n, i ) { - return i; - } ).length, - dependencyMismatch = false, - val = this.elementValue( element ), - result, method, rule; - - // If a normalizer is defined for this element, then - // call it to retreive the changed value instead - // of using the real one. - // Note that `this` in the normalizer is `element`. - if ( typeof rules.normalizer === "function" ) { - val = rules.normalizer.call( element, val ); - - if ( typeof val !== "string" ) { - throw new TypeError( "The normalizer should return a string value." ); - } - - // Delete the normalizer from rules to avoid treating - // it as a pre-defined method. - delete rules.normalizer; - } - - for ( method in rules ) { - rule = { method: method, parameters: rules[ method ] }; - try { - result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); - - // If a method indicates that the field is optional and therefore valid, - // don't mark it as valid when there are no other rules - if ( result === "dependency-mismatch" && rulesCount === 1 ) { - dependencyMismatch = true; - continue; - } - dependencyMismatch = false; - - if ( result === "pending" ) { - this.toHide = this.toHide.not( this.errorsFor( element ) ); - return; - } - - if ( !result ) { - this.formatAndAdd( element, rule ); - return false; - } - } catch ( e ) { - if ( this.settings.debug && window.console ) { - console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); - } - if ( e instanceof TypeError ) { - e.message += ". Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method."; - } - - throw e; - } - } - if ( dependencyMismatch ) { - return; - } - if ( this.objectLength( rules ) ) { - this.successList.push( element ); - } - return true; - }, - - // Return the custom message for the given element and validation method - // specified in the element's HTML5 data attribute - // return the generic message if present and no method specific message is present - customDataMessage: function( element, method ) { - return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + - method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); - }, - - // Return the custom message for the given element name and validation method - customMessage: function( name, method ) { - var m = this.settings.messages[ name ]; - return m && ( m.constructor === String ? m : m[ method ] ); - }, - - // Return the first defined argument, allowing empty strings - findDefined: function() { - for ( var i = 0; i < arguments.length; i++ ) { - if ( arguments[ i ] !== undefined ) { - return arguments[ i ]; - } - } - return undefined; - }, - - // The second parameter 'rule' used to be a string, and extended to an object literal - // of the following form: - // rule = { - // method: "method name", - // parameters: "the given method parameters" - // } - // - // The old behavior still supported, kept to maintain backward compatibility with - // old code, and will be removed in the next major release. - defaultMessage: function( element, rule ) { - if ( typeof rule === "string" ) { - rule = { method: rule }; - } - - var message = this.findDefined( - this.customMessage( element.name, rule.method ), - this.customDataMessage( element, rule.method ), - - // 'title' is never undefined, so handle empty string as undefined - !this.settings.ignoreTitle && element.title || undefined, - $.validator.messages[ rule.method ], - "Warning: No message defined for " + element.name + "" - ), - theregex = /\$?\{(\d+)\}/g; - if ( typeof message === "function" ) { - message = message.call( this, rule.parameters, element ); - } else if ( theregex.test( message ) ) { - message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); - } - - return message; - }, - - formatAndAdd: function( element, rule ) { - var message = this.defaultMessage( element, rule ); - - this.errorList.push( { - message: message, - element: element, - method: rule.method - } ); - - this.errorMap[ element.name ] = message; - this.submitted[ element.name ] = message; - }, - - addWrapper: function( toToggle ) { - if ( this.settings.wrapper ) { - toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); - } - return toToggle; - }, - - defaultShowErrors: function() { - var i, elements, error; - for ( i = 0; this.errorList[ i ]; i++ ) { - error = this.errorList[ i ]; - if ( this.settings.highlight ) { - this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); - } - this.showLabel( error.element, error.message ); - } - if ( this.errorList.length ) { - this.toShow = this.toShow.add( this.containers ); - } - if ( this.settings.success ) { - for ( i = 0; this.successList[ i ]; i++ ) { - this.showLabel( this.successList[ i ] ); - } - } - if ( this.settings.unhighlight ) { - for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { - this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); - } - } - this.toHide = this.toHide.not( this.toShow ); - this.hideErrors(); - this.addWrapper( this.toShow ).show(); - }, - - validElements: function() { - return this.currentElements.not( this.invalidElements() ); - }, - - invalidElements: function() { - return $( this.errorList ).map( function() { - return this.element; - } ); - }, - - showLabel: function( element, message ) { - var place, group, errorID, v, - error = this.errorsFor( element ), - elementID = this.idOrName( element ), - describedBy = $( element ).attr( "aria-describedby" ); - - if ( error.length ) { - - // Refresh error/success class - error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); - - // Replace message on existing label - error.html( message ); - } else { - - // Create error element - error = $( "<" + this.settings.errorElement + ">" ) - .attr( "id", elementID + "-error" ) - .addClass( this.settings.errorClass ) - .html( message || "" ); - - // Maintain reference to the element to be placed into the DOM - place = error; - if ( this.settings.wrapper ) { - - // Make sure the element is visible, even in IE - // actually showing the wrapped element is handled elsewhere - place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); - } - if ( this.labelContainer.length ) { - this.labelContainer.append( place ); - } else if ( this.settings.errorPlacement ) { - this.settings.errorPlacement.call( this, place, $( element ) ); - } else { - place.insertAfter( element ); - } - - // Link error back to the element - if ( error.is( "label" ) ) { - - // If the error is a label, then associate using 'for' - error.attr( "for", elementID ); - - // If the element is not a child of an associated label, then it's necessary - // to explicitly apply aria-describedby - } else if ( error.parents( "label[for='" + this.escapeCssMeta( elementID ) + "']" ).length === 0 ) { - errorID = error.attr( "id" ); - - // Respect existing non-error aria-describedby - if ( !describedBy ) { - describedBy = errorID; - } else if ( !describedBy.match( new RegExp( "\\b" + this.escapeCssMeta( errorID ) + "\\b" ) ) ) { - - // Add to end of list if not already present - describedBy += " " + errorID; - } - $( element ).attr( "aria-describedby", describedBy ); - - // If this element is grouped, then assign to all elements in the same group - group = this.groups[ element.name ]; - if ( group ) { - v = this; - $.each( v.groups, function( name, testgroup ) { - if ( testgroup === group ) { - $( "[name='" + v.escapeCssMeta( name ) + "']", v.currentForm ) - .attr( "aria-describedby", error.attr( "id" ) ); - } - } ); - } - } - } - if ( !message && this.settings.success ) { - error.text( "" ); - if ( typeof this.settings.success === "string" ) { - error.addClass( this.settings.success ); - } else { - this.settings.success( error, element ); - } - } - this.toShow = this.toShow.add( error ); - }, - - errorsFor: function( element ) { - var name = this.escapeCssMeta( this.idOrName( element ) ), - describer = $( element ).attr( "aria-describedby" ), - selector = "label[for='" + name + "'], label[for='" + name + "'] *"; - - // 'aria-describedby' should directly reference the error element - if ( describer ) { - selector = selector + ", #" + this.escapeCssMeta( describer ) - .replace( /\s+/g, ", #" ); - } - - return this - .errors() - .filter( selector ); - }, - - // See https://api.jquery.com/category/selectors/, for CSS - // meta-characters that should be escaped in order to be used with JQuery - // as a literal part of a name/id or any selector. - escapeCssMeta: function( string ) { - return string.replace( /([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g, "\\$1" ); - }, - - idOrName: function( element ) { - return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); - }, - - validationTargetFor: function( element ) { - - // If radio/checkbox, validate first element in group instead - if ( this.checkable( element ) ) { - element = this.findByName( element.name ); - } - - // Always apply ignore filter - return $( element ).not( this.settings.ignore )[ 0 ]; - }, - - checkable: function( element ) { - return ( /radio|checkbox/i ).test( element.type ); - }, - - findByName: function( name ) { - return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" ); - }, - - getLength: function( value, element ) { - switch ( element.nodeName.toLowerCase() ) { - case "select": - return $( "option:selected", element ).length; - case "input": - if ( this.checkable( element ) ) { - return this.findByName( element.name ).filter( ":checked" ).length; - } - } - return value.length; - }, - - depend: function( param, element ) { - return this.dependTypes[ typeof param ] ? this.dependTypes[ typeof param ]( param, element ) : true; - }, - - dependTypes: { - "boolean": function( param ) { - return param; - }, - "string": function( param, element ) { - return !!$( param, element.form ).length; - }, - "function": function( param, element ) { - return param( element ); - } - }, - - optional: function( element ) { - var val = this.elementValue( element ); - return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; - }, - - startRequest: function( element ) { - if ( !this.pending[ element.name ] ) { - this.pendingRequest++; - $( element ).addClass( this.settings.pendingClass ); - this.pending[ element.name ] = true; - } - }, - - stopRequest: function( element, valid ) { - this.pendingRequest--; - - // Sometimes synchronization fails, make sure pendingRequest is never < 0 - if ( this.pendingRequest < 0 ) { - this.pendingRequest = 0; - } - delete this.pending[ element.name ]; - $( element ).removeClass( this.settings.pendingClass ); - if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { - $( this.currentForm ).submit(); - this.formSubmitted = false; - } else if ( !valid && this.pendingRequest === 0 && this.formSubmitted ) { - $( this.currentForm ).triggerHandler( "invalid-form", [ this ] ); - this.formSubmitted = false; - } - }, - - previousValue: function( element, method ) { - method = typeof method === "string" && method || "remote"; - - return $.data( element, "previousValue" ) || $.data( element, "previousValue", { - old: null, - valid: true, - message: this.defaultMessage( element, { method: method } ) - } ); - }, - - // Cleans up all forms and elements, removes validator-specific events - destroy: function() { - this.resetForm(); - - $( this.currentForm ) - .off( ".validate" ) - .removeData( "validator" ) - .find( ".validate-equalTo-blur" ) - .off( ".validate-equalTo" ) - .removeClass( "validate-equalTo-blur" ); - } - - }, - - classRuleSettings: { - required: { required: true }, - email: { email: true }, - url: { url: true }, - date: { date: true }, - dateISO: { dateISO: true }, - number: { number: true }, - digits: { digits: true }, - creditcard: { creditcard: true } - }, - - addClassRules: function( className, rules ) { - if ( className.constructor === String ) { - this.classRuleSettings[ className ] = rules; - } else { - $.extend( this.classRuleSettings, className ); - } - }, - - classRules: function( element ) { - var rules = {}, - classes = $( element ).attr( "class" ); - - if ( classes ) { - $.each( classes.split( " " ), function() { - if ( this in $.validator.classRuleSettings ) { - $.extend( rules, $.validator.classRuleSettings[ this ] ); - } - } ); - } - return rules; - }, - - normalizeAttributeRule: function( rules, type, method, value ) { - - // Convert the value to a number for number inputs, and for text for backwards compability - // allows type="date" and others to be compared as strings - if ( /min|max|step/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { - value = Number( value ); - - // Support Opera Mini, which returns NaN for undefined minlength - if ( isNaN( value ) ) { - value = undefined; - } - } - - if ( value || value === 0 ) { - rules[ method ] = value; - } else if ( type === method && type !== "range" ) { - - // Exception: the jquery validate 'range' method - // does not test for the html5 'range' type - rules[ method ] = true; - } - }, - - attributeRules: function( element ) { - var rules = {}, - $element = $( element ), - type = element.getAttribute( "type" ), - method, value; - - for ( method in $.validator.methods ) { - - // Support for in both html5 and older browsers - if ( method === "required" ) { - value = element.getAttribute( method ); - - // Some browsers return an empty string for the required attribute - // and non-HTML5 browsers might have required="" markup - if ( value === "" ) { - value = true; - } - - // Force non-HTML5 browsers to return bool - value = !!value; - } else { - value = $element.attr( method ); - } - - this.normalizeAttributeRule( rules, type, method, value ); - } - - // 'maxlength' may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs - if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) { - delete rules.maxlength; - } - - return rules; - }, - - dataRules: function( element ) { - var rules = {}, - $element = $( element ), - type = element.getAttribute( "type" ), - method, value; - - for ( method in $.validator.methods ) { - value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); - this.normalizeAttributeRule( rules, type, method, value ); - } - return rules; - }, - - staticRules: function( element ) { - var rules = {}, - validator = $.data( element.form, "validator" ); - - if ( validator.settings.rules ) { - rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {}; - } - return rules; - }, - - normalizeRules: function( rules, element ) { - - // Handle dependency check - $.each( rules, function( prop, val ) { - - // Ignore rule when param is explicitly false, eg. required:false - if ( val === false ) { - delete rules[ prop ]; - return; - } - if ( val.param || val.depends ) { - var keepRule = true; - switch ( typeof val.depends ) { - case "string": - keepRule = !!$( val.depends, element.form ).length; - break; - case "function": - keepRule = val.depends.call( element, element ); - break; - } - if ( keepRule ) { - rules[ prop ] = val.param !== undefined ? val.param : true; - } else { - $.data( element.form, "validator" ).resetElements( $( element ) ); - delete rules[ prop ]; - } - } - } ); - - // Evaluate parameters - $.each( rules, function( rule, parameter ) { - rules[ rule ] = $.isFunction( parameter ) && rule !== "normalizer" ? parameter( element ) : parameter; - } ); - - // Clean number parameters - $.each( [ "minlength", "maxlength" ], function() { - if ( rules[ this ] ) { - rules[ this ] = Number( rules[ this ] ); - } - } ); - $.each( [ "rangelength", "range" ], function() { - var parts; - if ( rules[ this ] ) { - if ( $.isArray( rules[ this ] ) ) { - rules[ this ] = [ Number( rules[ this ][ 0 ] ), Number( rules[ this ][ 1 ] ) ]; - } else if ( typeof rules[ this ] === "string" ) { - parts = rules[ this ].replace( /[\[\]]/g, "" ).split( /[\s,]+/ ); - rules[ this ] = [ Number( parts[ 0 ] ), Number( parts[ 1 ] ) ]; - } - } - } ); - - if ( $.validator.autoCreateRanges ) { - - // Auto-create ranges - if ( rules.min != null && rules.max != null ) { - rules.range = [ rules.min, rules.max ]; - delete rules.min; - delete rules.max; - } - if ( rules.minlength != null && rules.maxlength != null ) { - rules.rangelength = [ rules.minlength, rules.maxlength ]; - delete rules.minlength; - delete rules.maxlength; - } - } - - return rules; - }, - - // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} - normalizeRule: function( data ) { - if ( typeof data === "string" ) { - var transformed = {}; - $.each( data.split( /\s/ ), function() { - transformed[ this ] = true; - } ); - data = transformed; - } - return data; - }, - - // http://jqueryvalidation.org/jQuery.validator.addMethod/ - addMethod: function( name, method, message ) { - $.validator.methods[ name ] = method; - $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ]; - if ( method.length < 3 ) { - $.validator.addClassRules( name, $.validator.normalizeRule( name ) ); - } - }, - - // http://jqueryvalidation.org/jQuery.validator.methods/ - methods: { - - // http://jqueryvalidation.org/required-method/ - required: function( value, element, param ) { - - // Check if dependency is met - if ( !this.depend( param, element ) ) { - return "dependency-mismatch"; - } - if ( element.nodeName.toLowerCase() === "select" ) { - - // Could be an array for select-multiple or a string, both are fine this way - var val = $( element ).val(); - return val && val.length > 0; - } - if ( this.checkable( element ) ) { - return this.getLength( value, element ) > 0; - } - return value.length > 0; - }, - - // http://jqueryvalidation.org/email-method/ - email: function( value, element ) { - - // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address - // Retrieved 2014-01-14 - // If you have a problem with this implementation, report a bug against the above spec - // Or use custom methods to implement your own email validation - return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value ); - }, - - // http://jqueryvalidation.org/url-method/ - url: function( value, element ) { - - // Copyright (c) 2010-2013 Diego Perini, MIT licensed - // https://gist.github.com/dperini/729294 - // see also https://mathiasbynens.be/demo/url-regex - // modified to allow protocol-relative URLs - return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); - }, - - // http://jqueryvalidation.org/date-method/ - date: function( value, element ) { - return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); - }, - - // http://jqueryvalidation.org/dateISO-method/ - dateISO: function( value, element ) { - return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); - }, - - // http://jqueryvalidation.org/number-method/ - number: function( value, element ) { - return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); - }, - - // http://jqueryvalidation.org/digits-method/ - digits: function( value, element ) { - return this.optional( element ) || /^\d+$/.test( value ); - }, - - // http://jqueryvalidation.org/minlength-method/ - minlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || length >= param; - }, - - // http://jqueryvalidation.org/maxlength-method/ - maxlength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || length <= param; - }, - - // http://jqueryvalidation.org/rangelength-method/ - rangelength: function( value, element, param ) { - var length = $.isArray( value ) ? value.length : this.getLength( value, element ); - return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); - }, - - // http://jqueryvalidation.org/min-method/ - min: function( value, element, param ) { - return this.optional( element ) || value >= param; - }, - - // http://jqueryvalidation.org/max-method/ - max: function( value, element, param ) { - return this.optional( element ) || value <= param; - }, - - // http://jqueryvalidation.org/range-method/ - range: function( value, element, param ) { - return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); - }, - - // http://jqueryvalidation.org/step-method/ - step: function( value, element, param ) { - var type = $( element ).attr( "type" ), - errorMessage = "Step attribute on input type " + type + " is not supported.", - supportedTypes = [ "text", "number", "range" ], - re = new RegExp( "\\b" + type + "\\b" ), - notSupported = type && !re.test( supportedTypes.join() ), - decimalPlaces = function( num ) { - var match = ( "" + num ).match( /(?:\.(\d+))?$/ ); - if ( !match ) { - return 0; - } - - // Number of digits right of decimal point. - return match[ 1 ] ? match[ 1 ].length : 0; - }, - toInt = function( num ) { - return Math.round( num * Math.pow( 10, decimals ) ); - }, - valid = true, - decimals; - - // Works only for text, number and range input types - // TODO find a way to support input types date, datetime, datetime-local, month, time and week - if ( notSupported ) { - throw new Error( errorMessage ); - } - - decimals = decimalPlaces( param ); - - // Value can't have too many decimals - if ( decimalPlaces( value ) > decimals || toInt( value ) % toInt( param ) !== 0 ) { - valid = false; - } - - return this.optional( element ) || valid; - }, - - // http://jqueryvalidation.org/equalTo-method/ - equalTo: function( value, element, param ) { - - // Bind to the blur event of the target in order to revalidate whenever the target field is updated - var target = $( param ); - if ( this.settings.onfocusout && target.not( ".validate-equalTo-blur" ).length ) { - target.addClass( "validate-equalTo-blur" ).on( "blur.validate-equalTo", function() { - $( element ).valid(); - } ); - } - return value === target.val(); - }, - - // http://jqueryvalidation.org/remote-method/ - remote: function( value, element, param, method ) { - if ( this.optional( element ) ) { - return "dependency-mismatch"; - } - - method = typeof method === "string" && method || "remote"; - - var previous = this.previousValue( element, method ), - validator, data, optionDataString; - - if ( !this.settings.messages[ element.name ] ) { - this.settings.messages[ element.name ] = {}; - } - previous.originalMessage = previous.originalMessage || this.settings.messages[ element.name ][ method ]; - this.settings.messages[ element.name ][ method ] = previous.message; - - param = typeof param === "string" && { url: param } || param; - optionDataString = $.param( $.extend( { data: value }, param.data ) ); - if ( previous.old === optionDataString ) { - return previous.valid; - } - - previous.old = optionDataString; - validator = this; - this.startRequest( element ); - data = {}; - data[ element.name ] = value; - $.ajax( $.extend( true, { - mode: "abort", - port: "validate" + element.name, - dataType: "json", - data: data, - context: validator.currentForm, - success: function( response ) { - var valid = response === true || response === "true", - errors, message, submitted; - - validator.settings.messages[ element.name ][ method ] = previous.originalMessage; - if ( valid ) { - submitted = validator.formSubmitted; - validator.resetInternals(); - validator.toHide = validator.errorsFor( element ); - validator.formSubmitted = submitted; - validator.successList.push( element ); - validator.invalid[ element.name ] = false; - validator.showErrors(); - } else { - errors = {}; - message = response || validator.defaultMessage( element, { method: method, parameters: value } ); - errors[ element.name ] = previous.message = message; - validator.invalid[ element.name ] = true; - validator.showErrors( errors ); - } - previous.valid = valid; - validator.stopRequest( element, valid ); - } - }, param ) ); - return "pending"; - } - } - -} ); - -// Ajax mode: abort -// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); -// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() - -var pendingRequests = {}, - ajax; - -// Use a prefilter if available (1.5+) -if ( $.ajaxPrefilter ) { - $.ajaxPrefilter( function( settings, _, xhr ) { - var port = settings.port; - if ( settings.mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } - pendingRequests[ port ] = xhr; - } - } ); -} else { - - // Proxy ajax - ajax = $.ajax; - $.ajax = function( settings ) { - var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, - port = ( "port" in settings ? settings : $.ajaxSettings ).port; - if ( mode === "abort" ) { - if ( pendingRequests[ port ] ) { - pendingRequests[ port ].abort(); - } - pendingRequests[ port ] = ajax.apply( this, arguments ); - return pendingRequests[ port ]; - } - return ajax.apply( this, arguments ); - }; -} -return $; -})); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.min.js b/src/Presentation/SmartStore.Web/Scripts/jquery.validate.min.js deleted file mode 100644 index 49bd55c426..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery Validation Plugin - v1.16.0 - 12/2/2016 - * http://jqueryvalidation.org/ - * Copyright (c) 2016 Jörn Zaefferer; Licensed MIT */ -!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.settings.submitHandler&&(c.submitButton=b.target),a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return!c.settings.submitHandler||(c.submitButton&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(null!=j&&null!=j.form){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}}),a.extend(a.expr.pseudos||a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){var c=a(b).val();return null!==c&&!!a.trim(""+c)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){!this.form&&this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0]);var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)a[b]&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.hasAttribute("contenteditable")&&(this.form=a(this).closest("form")[0]),!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type;return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=b.hasAttribute("contenteditable")?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);if("function"==typeof f.normalizer){if(i=f.normalizer.call(b,i),"string"!=typeof i)throw new TypeError("The normalizer should return a string value.");delete f.normalizer}for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j instanceof TypeError&&(j.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+""),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return a.replace(/([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(d,e){b[d]=a.isFunction(e)&&"normalizer"!==d?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e<=d},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var b,c={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a}); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Scripts/jquery.validate.unobtrusive.custom.js b/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.custom.js similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Scripts/jquery.validate.unobtrusive.custom.js rename to src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.custom.js diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.js b/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.js deleted file mode 100644 index 05033349d5..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.js +++ /dev/null @@ -1,429 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/*! -** Unobtrusive validation support library for jQuery and jQuery Validate -** Copyright (C) Microsoft Corporation. All rights reserved. -*/ - -/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ -/*global document: false, jQuery: false */ - -(function ($) { - var $jQval = $.validator, - adapters, - data_validation = "unobtrusiveValidation"; - - function setValidationValues(options, ruleName, value) { - options.rules[ruleName] = value; - if (options.message) { - options.messages[ruleName] = options.message; - } - } - - function splitAndTrim(value) { - return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); - } - - function escapeAttributeValue(value) { - // As mentioned on http://api.jquery.com/category/selectors/ - return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); - } - - function getModelPrefix(fieldName) { - return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); - } - - function appendModelPrefix(value, prefix) { - if (value.indexOf("*.") === 0) { - value = value.replace("*.", prefix); - } - return value; - } - - function onError(error, inputElement) { // 'this' is the form element - var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"), - replaceAttrValue = container.attr("data-valmsg-replace"), - replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; - - container.removeClass("field-validation-valid").addClass("field-validation-error"); - error.data("unobtrusiveContainer", container); - - if (replace) { - container.empty(); - error.removeClass("input-validation-error").appendTo(container); - } - else { - error.hide(); - } - } - - function onErrors(event, validator) { // 'this' is the form element - var container = $(this).find("[data-valmsg-summary=true]"), - list = container.find("ul"); - - if (list && list.length && validator.errorList.length) { - list.empty(); - container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); - - $.each(validator.errorList, function () { - $("
          • ").html(this.message).appendTo(list); - }); - } - } - - function onSuccess(error) { // 'this' is the form element - var container = error.data("unobtrusiveContainer"), - replaceAttrValue = container.attr("data-valmsg-replace"), - replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; - - if (container) { - container.addClass("field-validation-valid").removeClass("field-validation-error"); - error.removeData("unobtrusiveContainer"); - - if (replace) { - container.empty(); - } - } - } - - function onReset(event) { // 'this' is the form element - var $form = $(this), - key = '__jquery_unobtrusive_validation_form_reset'; - if ($form.data(key)) { - return; - } - // Set a flag that indicates we're currently resetting the form. - $form.data(key, true); - try { - $form.data("validator").resetForm(); - } finally { - $form.removeData(key); - } - - $form.find(".validation-summary-errors") - .addClass("validation-summary-valid") - .removeClass("validation-summary-errors"); - $form.find(".field-validation-error") - .addClass("field-validation-valid") - .removeClass("field-validation-error") - .removeData("unobtrusiveContainer") - .find(">*") // If we were using valmsg-replace, get the underlying error - .removeData("unobtrusiveContainer"); - } - - function validationInfo(form) { - var $form = $(form), - result = $form.data(data_validation), - onResetProxy = $.proxy(onReset, form), - defaultOptions = $jQval.unobtrusive.options || {}, - execInContext = function (name, args) { - var func = defaultOptions[name]; - func && $.isFunction(func) && func.apply(form, args); - } - - if (!result) { - result = { - options: { // options structure passed to jQuery Validate's validate() method - errorClass: defaultOptions.errorClass || "input-validation-error", - errorElement: defaultOptions.errorElement || "span", - errorPlacement: function () { - onError.apply(form, arguments); - execInContext("errorPlacement", arguments); - }, - invalidHandler: function () { - onErrors.apply(form, arguments); - execInContext("invalidHandler", arguments); - }, - messages: {}, - rules: {}, - success: function () { - onSuccess.apply(form, arguments); - execInContext("success", arguments); - } - }, - attachValidation: function () { - $form - .off("reset." + data_validation, onResetProxy) - .on("reset." + data_validation, onResetProxy) - .validate(this.options); - }, - validate: function () { // a validation function that is called by unobtrusive Ajax - $form.validate(); - return $form.valid(); - } - }; - $form.data(data_validation, result); - } - - return result; - } - - $jQval.unobtrusive = { - adapters: [], - - parseElement: function (element, skipAttach) { - /// - /// Parses a single HTML element for unobtrusive validation attributes. - /// - /// The HTML element to be parsed. - /// [Optional] true to skip attaching the - /// validation to the form. If parsing just this single element, you should specify true. - /// If parsing several elements, you should specify false, and manually attach the validation - /// to the form when you are finished. The default is false. - var $element = $(element), - form = $element.parents("form")[0], - valInfo, rules, messages; - - if (!form) { // Cannot do client-side validation without a form - return; - } - - valInfo = validationInfo(form); - valInfo.options.rules[element.name] = rules = {}; - valInfo.options.messages[element.name] = messages = {}; - - $.each(this.adapters, function () { - var prefix = "data-val-" + this.name, - message = $element.attr(prefix), - paramValues = {}; - - if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) - prefix += "-"; - - $.each(this.params, function () { - paramValues[this] = $element.attr(prefix + this); - }); - - this.adapt({ - element: element, - form: form, - message: message, - params: paramValues, - rules: rules, - messages: messages - }); - } - }); - - $.extend(rules, { "__dummy__": true }); - - if (!skipAttach) { - valInfo.attachValidation(); - } - }, - - parse: function (selector) { - /// - /// Parses all the HTML elements in the specified selector. It looks for input elements decorated - /// with the [data-val=true] attribute value and enables validation according to the data-val-* - /// attribute values. - /// - /// Any valid jQuery selector. - - // $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one - // element with data-val=true - var $selector = $(selector), - $forms = $selector.parents() - .addBack() - .filter("form") - .add($selector.find("form")) - .has("[data-val=true]"); - - $selector.find("[data-val=true]").each(function () { - $jQval.unobtrusive.parseElement(this, true); - }); - - $forms.each(function () { - var info = validationInfo(this); - if (info) { - info.attachValidation(); - } - }); - } - }; - - adapters = $jQval.unobtrusive.adapters; - - adapters.add = function (adapterName, params, fn) { - /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. - /// The name of the adapter to be added. This matches the name used - /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). - /// [Optional] An array of parameter names (strings) that will - /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and - /// mmmm is the parameter name). - /// The function to call, which adapts the values from the HTML - /// attributes into jQuery Validate rules and/or messages. - /// - if (!fn) { // Called with no params, just a function - fn = params; - params = []; - } - this.push({ name: adapterName, params: params, adapt: fn }); - return this; - }; - - adapters.addBool = function (adapterName, ruleName) { - /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where - /// the jQuery Validate validation rule has no parameter values. - /// The name of the adapter to be added. This matches the name used - /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). - /// [Optional] The name of the jQuery Validate rule. If not provided, the value - /// of adapterName will be used instead. - /// - return this.add(adapterName, function (options) { - setValidationValues(options, ruleName || adapterName, true); - }); - }; - - adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { - /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where - /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and - /// one for min-and-max). The HTML parameters are expected to be named -min and -max. - /// The name of the adapter to be added. This matches the name used - /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). - /// The name of the jQuery Validate rule to be used when you only - /// have a minimum value. - /// The name of the jQuery Validate rule to be used when you only - /// have a maximum value. - /// The name of the jQuery Validate rule to be used when you - /// have both a minimum and maximum value. - /// [Optional] The name of the HTML attribute that - /// contains the minimum value. The default is "min". - /// [Optional] The name of the HTML attribute that - /// contains the maximum value. The default is "max". - /// - return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { - var min = options.params.min, - max = options.params.max; - - if (min && max) { - setValidationValues(options, minMaxRuleName, [min, max]); - } - else if (min) { - setValidationValues(options, minRuleName, min); - } - else if (max) { - setValidationValues(options, maxRuleName, max); - } - }); - }; - - adapters.addSingleVal = function (adapterName, attribute, ruleName) { - /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where - /// the jQuery Validate validation rule has a single value. - /// The name of the adapter to be added. This matches the name used - /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). - /// [Optional] The name of the HTML attribute that contains the value. - /// The default is "val". - /// [Optional] The name of the jQuery Validate rule. If not provided, the value - /// of adapterName will be used instead. - /// - return this.add(adapterName, [attribute || "val"], function (options) { - setValidationValues(options, ruleName || adapterName, options.params[attribute]); - }); - }; - - $jQval.addMethod("__dummy__", function (value, element, params) { - return true; - }); - - $jQval.addMethod("regex", function (value, element, params) { - var match; - if (this.optional(element)) { - return true; - } - - match = new RegExp(params).exec(value); - return (match && (match.index === 0) && (match[0].length === value.length)); - }); - - $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { - var match; - if (nonalphamin) { - match = value.match(/\W/g); - match = match && match.length >= nonalphamin; - } - return match; - }); - - if ($jQval.methods.extension) { - adapters.addSingleVal("accept", "mimtype"); - adapters.addSingleVal("extension", "extension"); - } else { - // for backward compatibility, when the 'extension' validation method does not exist, such as with versions - // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for - // validating the extension, and ignore mime-type validations as they are not supported. - adapters.addSingleVal("extension", "extension", "accept"); - } - - adapters.addSingleVal("regex", "pattern"); - adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); - adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); - adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); - adapters.add("equalto", ["other"], function (options) { - var prefix = getModelPrefix(options.element.name), - other = options.params.other, - fullOtherName = appendModelPrefix(other, prefix), - element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0]; - - setValidationValues(options, "equalTo", element); - }); - adapters.add("required", function (options) { - // jQuery Validate equates "required" with "mandatory" for checkbox elements - if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { - setValidationValues(options, "required", true); - } - }); - adapters.add("remote", ["url", "type", "additionalfields"], function (options) { - var value = { - url: options.params.url, - type: options.params.type || "GET", - data: {} - }, - prefix = getModelPrefix(options.element.name); - - $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { - var paramName = appendModelPrefix(fieldName, prefix); - value.data[paramName] = function () { - var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); - // For checkboxes and radio buttons, only pick up values from checked fields. - if (field.is(":checkbox")) { - return field.filter(":checked").val() || field.filter(":hidden").val() || ''; - } - else if (field.is(":radio")) { - return field.filter(":checked").val() || ''; - } - return field.val(); - }; - }); - - setValidationValues(options, "remote", value); - }); - adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { - if (options.params.min) { - setValidationValues(options, "minlength", options.params.min); - } - if (options.params.nonalphamin) { - setValidationValues(options, "nonalphamin", options.params.nonalphamin); - } - if (options.params.regex) { - setValidationValues(options, "regex", options.params.regex); - } - }); - - $(function () { - $jQval.unobtrusive.parse(document); - }); -}(jQuery)); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.min.js b/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.min.js deleted file mode 100644 index dfeaf38ec8..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/jquery.validate.unobtrusive.min.js +++ /dev/null @@ -1,19 +0,0 @@ -/* NUGET: BEGIN LICENSE TEXT - * - * Microsoft grants you the right to use these script files for the sole - * purpose of either: (i) interacting through your browser with the Microsoft - * website or online service, subject to the applicable licensing or use - * terms; or (ii) using the files as included with a Microsoft product subject - * to that product's license terms. Microsoft reserves all other rights to the - * files not expressly granted by Microsoft, whether by implication, estoppel - * or otherwise. Insofar as a script file is dual licensed under GPL, - * Microsoft neither took the code under GPL nor distributes it thereunder but - * under the terms set out in this paragraph. All notices and licenses - * below are for informational purposes only. - * - * NUGET: END LICENSE TEXT */ -/* -** Unobtrusive validation support library for jQuery and jQuery Validate -** Copyright (C) Microsoft Corporation. All rights reserved. -*/ -(function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
          • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.menu-aim.js b/src/Presentation/SmartStore.Web/Scripts/legacy/jquery.menu-aim.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/jquery.menu-aim.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/jquery.menu-aim.js diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.pnotify.js b/src/Presentation/SmartStore.Web/Scripts/legacy/jquery.pnotify.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/jquery.pnotify.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/jquery.pnotify.js diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.preload.js b/src/Presentation/SmartStore.Web/Scripts/legacy/jquery.preload.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/jquery.preload.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/jquery.preload.js diff --git a/src/Presentation/SmartStore.Web/Scripts/jquery.transit.js b/src/Presentation/SmartStore.Web/Scripts/legacy/jquery.transit.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/jquery.transit.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/jquery.transit.js diff --git a/src/Presentation/SmartStore.Web/Scripts/prettify.js b/src/Presentation/SmartStore.Web/Scripts/legacy/prettify.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/prettify.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/prettify.js diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.shrinkmenu.js b/src/Presentation/SmartStore.Web/Scripts/legacy/smartstore.shrinkmenu.js similarity index 100% rename from src/Presentation/SmartStore.Web/Scripts/smartstore.shrinkmenu.js rename to src/Presentation/SmartStore.Web/Scripts/legacy/smartstore.shrinkmenu.js diff --git a/src/Presentation/SmartStore.Web/Scripts/public.common.js b/src/Presentation/SmartStore.Web/Scripts/public.common.js index 628c915566..5374e45c46 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.common.js @@ -1,5 +1,7 @@ (function ($, window, document, undefined) { + var viewport = ResponsiveBootstrapToolkit; + window.displayAjaxLoading = function(display) { if ($.throbber === undefined) return; @@ -13,31 +15,132 @@ } window.getPageWidth = function() { - return parseFloat($("#content").css("width")); + return parseFloat($("#page").css("width")); + } + + window.getViewport = function () { + return viewport; } var _commonPluginFactories = [ // select2 function (ctx) { - if (!Modernizr.touchevents) { - if ($.fn.select2 === undefined || $.fn.selectWrapper === undefined) - return; - ctx.find("select:not(.noskin), input:hidden[data-select]").selectWrapper(); - } + if ($.fn.select2 === undefined || $.fn.selectWrapper === undefined) + return; + ctx.find("select:not(.noskin), input:hidden[data-select]").selectWrapper(); }, // tooltips function (ctx) { if ($.fn.tooltip === undefined) return; if (!Modernizr.touchevents) { - ctx.tooltip({ selector: "a[rel=tooltip], .tooltip-toggle" }); + ctx.tooltip({ selector: '[data-toggle=tooltip], .tooltip-toggle', animation: false }); } }, - // column equalizer + // touch spin function (ctx) { - if ($.fn.equalizeColumns === undefined) + if ($.fn.TouchSpin === undefined) return; - ctx.find(".equalized-column").equalizeColumns({ /*deep: true,*/ responsive: true }); + + ctx.find('.qty-input > .form-control').each(function (i, el) { + var ctl = $(this); + + ctl.TouchSpin({ + buttondown_class: 'btn btn-secondary', + buttonup_class: 'btn btn-secondary', + buttondown_txt: '', + buttonup_txt: '', + }); + }); + }, + // newsletter subsription + function (ctx) { + var newsletterContainer = $(".footer-newsletter"); + if (newsletterContainer.length > 0) + { + var url = newsletterContainer.data("subscription-url"); + + newsletterContainer.find('#newsletter-subscribe-button').on("click", function () { + var email = $("#newsletter-email").val(); + var subscribe = 'true'; + var resultDisplay = $("#newsletter-result-block"); + var elemGdprConsent = $(".footer-newsletter #GdprConsent") + var gdprConsent = elemGdprConsent.length == 0 ? null : elemGdprConsent.is(':checked'); + + if ($('#newsletter-unsubscribe').is(':checked')) { + subscribe = 'false'; + } + + $.ajax({ + cache: false, + type: "POST", + url: url, + data: { "subscribe": subscribe, "email": email, "GdprConsent": subscribe == 'true' ? gdprConsent : true }, + success: function (data) { + resultDisplay.html(data.Result); + if (data.Success) { + $('#newsletter-subscribe-block').hide(); + resultDisplay.removeClass("alert-danger d-none").addClass("alert-success d-block"); + } + else { + if (data.Result != "") + resultDisplay.removeClass("alert-success d-none").addClass("alert-danger d-block").fadeIn("slow").delay(2000).fadeOut("slow"); + } + }, + error:function (xhr, ajaxOptions, thrownError){ + resultDisplay.empty().text("Failed to subscribe").removeClass("alert-success d-none").addClass("alert-danger d-block"); + } + }); + return false; + }); + } + }, + // slick carousel + function (ctx) { + if ($.fn.slick === undefined) + return; + + ctx.find('.artlist-carousel > .artlist-grid').each(function (i, el) { + var list = $(this); + + list.slick({ + infinite: false, + rtl: $("html").attr("dir") == "rtl", + dots: true, + cssEase: 'ease-in-out', + speed: 300, + useCSS: true, + useTransform: true, + waitForAnimate: true, + prevArrow: '', + nextArrow: '', + respondTo: 'slider', + slidesToShow: 6, + slidesToScroll: 6, + responsive: [ + { + breakpoint: 280, + settings: { slidesToShow: 1, slidesToScroll: 1 } + }, + { + breakpoint: 440, + settings: { slidesToShow: 2, slidesToScroll: 2 } + }, + { + breakpoint: 640, + settings: { slidesToShow: 3, slidesToScroll: 3 } + }, + { + breakpoint: 780, + settings: { slidesToShow: 4, slidesToScroll: 4 } + }, + { + breakpoint: 960, + settings: { slidesToShow: 5, slidesToScroll: 5 } + }, + ] + }); + }); } ]; @@ -55,63 +158,20 @@ // on document ready // TODO: reorganize > public.globalinit.js $(function () { - - // adjust pnotify global defaults - if ($.pnotify) { - - // intercept window.alert with pnotify - window.alert = function (message) { - if (message == null || message.length <= 0) - return; - - $.pnotify({ - title: window.Res["Common.Notification"], - text: message, - type: "info", - animate_speed: 'fast', - closer_hover: false, - stack: false, - before_open: function (pnotify) { - // Position this notice in the center of the screen. - pnotify.css({ - "top": ($(window).height() / 2) - (pnotify.height() / 2), - "left": ($(window).width() / 2) - (pnotify.width() / 2) - }); - } - }); - } - } - - // notify subscribers about page/content width change + // Notify subscribers about page/content width change if (window.EventBroker) { - pageWidth = getPageWidth(); // initial width - $(window).on("resize", function () { - // check if page width has changed - var newWidth = getPageWidth(); - if (newWidth !== pageWidth) { - // ...and publish event - EventBroker.publish("page.resized", { oldWidth: pageWidth, newWidth: newWidth }); - pageWidth = newWidth; - } - }); + var currentContentWidth = $('#content').width(); + $(window).on('resize', function () { + var contentWidth = $('#content').width(); + if (contentWidth !== currentContentWidth) { + currentContentWidth = contentWidth; + console.debug("Grid tier changed: " + viewport.current()); + EventBroker.publish("page.resized", viewport); + } + }); } - - // create navbar - if ($.fn.navbar) - { - $('.navbar ul.nav-smart > li.dropdown').navbar(); - } - - // shrink menu - if ($.fn.shrinkMenu) { - $(".shrink-menu").shrinkMenu({ responsive: true }); - } - applyCommonPlugins($("body")); - - //$("select:not(.noskin), input:hidden[data-select]").selectWrapper(); - }); })( jQuery, this, document ); diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-cart.js b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-cart.js similarity index 92% rename from src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-cart.js rename to src/Presentation/SmartStore.Web/Scripts/public.offcanvas-cart.js index 4c4bfc8c33..ba04047633 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-cart.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-cart.js @@ -186,27 +186,38 @@ $(function () { }); // React to touchspin change - $('#offcanvas-cart').on('change', '.qty-input .form-control', function (e) { - var el = $(this); - $.ajax({ - cache: false, - type: "POST", - url: el.data("update-url"), - data: { "sciItemId": el.data("sci-id"), "newQuantity": el.val() }, - success: function (data) { - if (data.success == true) { - var type = el.data("type"); - ShopBar.loadSummary(type, true); - el.closest('.tab-pane').find('.sub-total').html(data.SubTotal); - } - else { - $(data.message).each(function (index, value) { - displayNotification(value, "error", false); - }); - } - } - }); - }); + var updatingCart = false; + var debouncedSpin = _.debounce(function (e) { + if (updatingCart) + return; + + updatingCart = true; + var el = $(this); + + $.ajax({ + cache: false, + type: "POST", + url: el.data("update-url"), + data: { "sciItemId": el.data("sci-id"), "newQuantity": el.val() }, + success: function (data) { + if (data.success == true) { + var type = el.data("type"); + ShopBar.loadSummary(type, true); + el.closest('.tab-pane').find('.sub-total').html(data.SubTotal); + } + else { + $(data.message).each(function (index, value) { + displayNotification(value, "error", false); + }); + } + }, + complete: function () { + updatingCart = false; + } + }); + }, 350, false); + + $('#offcanvas-cart').on('change', '.qty-input .form-control', debouncedSpin); }); var ShopBar = (function($) { @@ -293,7 +304,7 @@ var ShopBar = (function($) { }, showThrobber: function () { - var cnt = $(".tab-content", offcanvasCart); + var cnt = $(".tab-content", offcanvasCart); var throbber = cnt.data('throbber'); if (!throbber) { throbber = cnt.throbber({ white: true, small: true, message: '', show: false, speed: 0 }).data('throbber'); diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-menu.js b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js similarity index 77% rename from src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-menu.js rename to src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js index ad4eda12c8..e8d3785e85 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.offcanvas-menu.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.offcanvas-menu.js @@ -11,7 +11,8 @@ var AjaxMenu = (function ($, window, document, undefined) { var selectedMenuItemId = 0; var currentCategoryId = 0; var currentProductId = 0; - var currentManufacturerId = 0; + var currentManufacturerId = 0; + var publicStoreNavigationAllowed = true; var menu = $("#offcanvas-menu #menu-container"); @@ -47,12 +48,14 @@ var AjaxMenu = (function ($, window, document, undefined) { }); // menu click events - menu.on('click', '.ocm-item', function (e) { + menu.on('click', '.ocm-item', function (e) { + e.preventDefault(); + var item = $(this); var categoryId = item.data("id"); var isAjaxNavigation = item.data("ajax"); - if (isAjaxNavigation == false) { + if (!isAjaxNavigation) { window.setLocation(item.find(".ocm-link").attr("href")); return true; } @@ -62,34 +65,28 @@ var AjaxMenu = (function ($, window, document, undefined) { return false; } - item.addClass("animated"); - - e.preventDefault(); - - navigateToMenuItem(categoryId ? categoryId : 0, item.hasClass("navigate-back") ? "left" : "right"); + item.addClass("animated"); + + navigateToMenuItem(categoryId || 0, item.hasClass("navigate-back") ? "out" : "in"); - // for stopping event propagation + // stop event propagation return false; }); }); function navigateToMenuItem(categoryId, direction) { - - var categoryContainer = menu.find(".category-container"); - var firstCall = categoryContainer.length == 0; - var categoryTab = categoryId != 0 ? menu : menu.find("#ocm-categories"); - - var currentLayer = $(".layer.show", menu); - var nextLayer = currentLayer.next(); - var prevLayer = currentLayer.prev(); - - if (direction == "left") { - + var cnt = menu.find(".category-container"), + firstCall = cnt.length == 0, + categoryTab = categoryId != 0 ? menu : menu.find("#ocm-categories"), + currentLayer = $(".layer.show", menu), + nextLayer = currentLayer.next(), + prevLayer = currentLayer.prev(); + + if (direction === "out") { // check whether a previous layer exists (if it exists, its always the right one to navigate to) if (prevLayer.length > 0) { // special treatment when navigating back to home layer var isHome = prevLayer.hasClass("ocm-home-layer"); - if (isHome) { prevLayer .find(".ocm-nav-layer") @@ -103,8 +100,7 @@ var AjaxMenu = (function ($, window, document, undefined) { // if no previous layer exists, make ajax call and prepend response } - else if (direction == "right") { - + else if (direction === "in") { // check whether a next layer exists and if it has the same id as the element to which the user is navigating to if (nextLayer.data("id") == categoryId) { currentLayer.removeClass("show"); @@ -126,8 +122,7 @@ var AjaxMenu = (function ($, window, document, undefined) { "currentProductId": currentProductId }, type: 'POST', - success: function (response) { - + success: function (response) { // replace current menu content with response if (firstCall) { if (categoryId != 0) @@ -137,47 +132,44 @@ var AjaxMenu = (function ($, window, document, undefined) { } } else { + var cntSlideIn; + var cntSlideOut = currentLayer; - var categoryContainerSlideIn; - var categoryContainerSlideOut = currentLayer; - - if (direction == "left") { - - if (categoryId == 0) { + if (direction === "out") { + if (categoryId == 0) { navigateToHomeLayer(true); return; } - categoryContainerSlideIn = $(wrapAjaxResponse(response, "", categoryId)).prependTo(categoryTab); + cntSlideIn = $(wrapAjaxResponse(response, "", categoryId)).prependTo(categoryTab); } else { - categoryContainerSlideIn = $(wrapAjaxResponse(response, "", categoryId)).appendTo(categoryTab); + cntSlideIn = $(wrapAjaxResponse(response, "", categoryId)).appendTo(categoryTab); } - + _.delay(function () { - categoryContainerSlideIn.addClass("show"); - - if (direction !== undefined) - categoryContainerSlideOut.removeClass("show"); - + cntSlideIn.addClass("show"); + + if (direction) + cntSlideOut.removeClass("show"); }, 100); - if (direction == undefined) { - categoryContainerSlideIn = $(".ocm-home-layer"); - categoryContainerSlideOut = nextLayer; + if (!direction) { + cntSlideIn = $(".ocm-home-layer"); + cntSlideOut = nextLayer; - categoryContainerSlideIn + cntSlideIn .addClass("show") .find(".ocm-nav-layer") .removeClass("offcanvas-scrollable ocm-nav-layer layer"); - categoryContainerSlideOut.removeClass("show"); + cntSlideOut.removeClass("show"); } - categoryContainerSlideIn.on(Prefixer.event.transitionEnd, function (e) { - categoryContainerSlideOut.find(".ocm-item").removeClass("animated"); - categoryContainerSlideIn.find(".ocm-item").removeClass("animated"); - }); + cntSlideIn.on(Prefixer.event.transitionEnd, function (e) { + cntSlideOut.find(".ocm-item").removeClass("animated"); + cntSlideIn.find(".ocm-item").removeClass("animated"); + }); } }, error: function (jqXHR, textStatus, errorThrown) { @@ -194,23 +186,26 @@ var AjaxMenu = (function ($, window, document, undefined) { tabContent.tab('show'); return; } - + $.ajax({ cache: false, url: menu.data("url-home"), type: 'POST', success: function (response) { if (isBackward) { - response = response.replace("ocm-home-layer layer in", "ocm-home-layer layer"); + response = response.replace("ocm-home-layer layer show", "ocm-home-layer layer"); } - - menu.prepend(response); + + menu.prepend(response); tabContent = menu.find("#category-tab"); tabContent.tab('show'); navigateToMenuItem(0); AjaxMenu.initFooter(); - tabContent.data("initialized", true); + tabContent.data("initialized", true); + + if (publicStoreNavigationAllowed == false) + navigateToService(); }, error: function (jqXHR, textStatus, errorThrown) { console.log(errorThrown); @@ -264,8 +259,9 @@ var AjaxMenu = (function ($, window, document, undefined) { menuContent.find("[data-toggle=dropdown]").removeAttr("data-toggle"); // open MyAccount dropdown initially - var myAccount = menuContent.find("#menubar-my-account"); - myAccount.find(".dropdown").addClass("show"); + var myAccount = menuContent.find("#menubar-my-account"); + myAccount.find(".dropdown").addClass("openend"); + myAccount.find(".dropdown-menu").addClass("show"); // place MyAccount menu on top menuContent.prepend(myAccount); @@ -276,12 +272,15 @@ var AjaxMenu = (function ($, window, document, undefined) { // handle dropdown opening serviceTab.on("click", ".dropdown > .menubar-link", function (e) { - var dropdown = $(this).parent(); - if (dropdown.find(".dropdown-menu").length == 0) + var dropdown = $(this).parent(); + var dropdownMenu = dropdown.find(".dropdown-menu"); + + if (dropdownMenu.length == 0) return true; e.preventDefault(); - dropdown.toggleClass("show"); + dropdown.toggleClass("openend"); + dropdownMenu.toggleClass("show"); return false; }); @@ -310,14 +309,15 @@ var AjaxMenu = (function ($, window, document, undefined) { currentCategoryId = nav.data("current-category-id"); currentProductId = nav.data("current-product-id"); currentManufacturerId = nav.data("current-manufacturer-id"); - - if (selectedMenuItemId == 0) { - navigateToHomeLayer(false); - } - else { - navigateToMenuItem(selectedMenuItemId); - } - + publicStoreNavigationAllowed = menu.data("public-store-navigation-allowed"); + + if (selectedMenuItemId == 0) { + navigateToHomeLayer(false); + } + else { + navigateToMenuItem(selectedMenuItemId); + } + isInitialised = true; return; }, @@ -333,7 +333,7 @@ var AjaxMenu = (function ($, window, document, undefined) { var languageOptions = ""; var currencyOptions = ""; - if (!displayCurrencySelector && !displayCurrencySelector) + if (!displayCurrencySelector && !displayLanguageSelector) return; else footer.removeClass("d-none"); @@ -361,10 +361,7 @@ var AjaxMenu = (function ($, window, document, undefined) { $(".form-control", ocmLanguageSelector).append(languageOptions); } - - // skin select - applyCommonPlugins(footer); - + // on change navigate to value $(footer).find(".form-control").on("change", function (e) { var select = $(this); diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.product.js b/src/Presentation/SmartStore.Web/Scripts/public.product.js similarity index 87% rename from src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.product.js rename to src/Presentation/SmartStore.Web/Scripts/public.product.js index 5178a0463e..d659469c4d 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/public.product.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.product.js @@ -19,8 +19,9 @@ this.createGallery(opts.galleryStartIndex); // Update product data and gallery - $(el).find(':input').change(function (e) { - var ctx = $(this).closest('.update-container'); + $(el).on('change', ':input', function (e) { + var ctx = $(this).closest('.update-container'); + var isTouchSpin = $(this).parent(".bootstrap-touchspin").length > 0; if (ctx.length == 0) { // associated or bundled item @@ -30,14 +31,14 @@ ctx.doAjax({ data: ctx.find(':input').serialize(), callbackSuccess: function (response) { - self.updateDetailData(response, ctx); + self.updateDetailData(response, ctx, isTouchSpin); if (ctx.hasClass('pd-bundle-item')) { // update bundle price too $('#main-update-container').doAjax({ data: $('.pd-bundle-items').find(':input').serialize(), callbackSuccess: function (response2) { - self.updateDetailData(response2, $('#main-update-container')); + self.updateDetailData(response2, $('#main-update-container'), isTouchSpin); } }); } @@ -48,7 +49,7 @@ return this; }; - this.updateDetailData = function (data, ctx) { + this.updateDetailData = function (data, ctx, isTouchSpin) { var gallery = $('#pd-gallery').data(galPluginName); // Image gallery needs special treatment @@ -68,7 +69,7 @@ // Iterate all elems with [data-partial] attribute... var $el = $(el); var partial = $el.data('partial'); - if (partial) { + if (partial && !(isTouchSpin && partial == 'OfferActions')) { // ...fetch the updated html from the corresponding AJAX result object's properties if (data.Partials && data.Partials.hasOwnProperty(partial)) { var updatedHtml = data.Partials[partial] || ""; @@ -78,6 +79,8 @@ } }); + applyCommonPlugins(ctx); + ctx.find(".pd-tierprices").html(data.Partials["TierPrices"]); if (data.DynamicThumblUrl && data.DynamicThumblUrl.length > 0) { diff --git a/src/Presentation/SmartStore.Web/Scripts/public.reviews.js b/src/Presentation/SmartStore.Web/Scripts/public.reviews.js index d1a8786741..3a5d526d02 100644 --- a/src/Presentation/SmartStore.Web/Scripts/public.reviews.js +++ b/src/Presentation/SmartStore.Web/Scripts/public.reviews.js @@ -4,11 +4,11 @@ $(function () { - $(".product-review-item").on("click", ".vote", function (e) { + $(".review-list").on("click", ".review-vote-link", function (e) { var el = $(this); var reviewId = el.parent().data("review-id"); var href = el.parent().data("href"); - var isNo = el.hasClass("vote-no"); + var isNo = el.is(".review-vote-link-no"); setProductReviewHelpfulness(reviewId, isNo ? 'false' : 'true'); diff --git a/src/Presentation/SmartStore.Web/Scripts/public.search.js b/src/Presentation/SmartStore.Web/Scripts/public.search.js new file mode 100644 index 0000000000..1ca35eff14 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Scripts/public.search.js @@ -0,0 +1,407 @@ +(function ($, window, document, undefined) { + + // + // Instant Search + // ========================================================== + + $(function () { + $('form.instasearch-form').each(function () { + var form = $(this), + box = form.find('.instasearch-term'), + spinner = $('#instasearch-progress'); + + if (box.length == 0 || box.data('instasearch') === false) + return; + + var drop = form.find('.instasearch-drop'), + logo = $('.shop-logo'), + dropBody = drop.find('.instasearch-drop-body'), + minLength = box.data("minlength"), + url = box.data("url"), + keyNav = null; + + box.parent().on('click', function (e) { + e.stopPropagation(); + }); + + box.on('focus', function (e) { + expandBox(); + }); + + box.on('keydown', function (e) { + if (e.which == 13 /* Enter */) { + if (keyNav && dropBody.find('.key-hover').length > 0) { + // Do not post form when key navigation is in progress + e.preventDefault(); + } + } + }); + + box.on('keyup', function (e) { + if (e.which == 27 /* ESC */) { + closeDrop(); + } + }); + + $(document).on('mousedown', function (e) { + // Close drop on outside click + if ($(e.target).closest('.instasearch-form').length > 0) + return; + + shrinkBox(); + closeDrop(); + }); + + var debouncedInput = _.debounce(function (e) { + doSearch(box.val()); + }, 180, false); + box.on('input propertychange paste', debouncedInput); + + // Sometimes a previous search term finishes request AFTER + // a subsequent one. We need to skip rendering in this case. + var lastTerm; + + function doSearch(term) { + if (term.length < minLength) { + closeDrop(); + dropBody.html(''); + return; + } + + if (spinner.length === 0) { + spinner = createCircularSpinner(20).attr('id', 'instasearch-progress').appendTo(box.parent()); + } + // Don't show spinner when result is coming fast (< 100 ms.) + var spinnerTimeout = setTimeout(function () { spinner.addClass('active'); }, 100) + + // save last entered term in a global variable + lastTerm = term; + + $.ajax({ + dataType: "html", + url: url, + data: { q: term }, + type: 'POST', + //cache: true, + success: function (html, status, req) { + if (lastTerm !== term) { + // This is the result of a previous term. Get out! + return; + } + + if (_.str.isBlank(html)) { + closeDrop(); + dropBody.html(''); + } + else { + var markup = $(html); + var isMultiCol = markup.hasClass('instasearch-row'); + drop.toggleClass('w-100', !isMultiCol); + dropBody.html(markup); + openDrop(); + } + }, + error: function () { + closeDrop(); + dropBody.html(''); + }, + complete: function () { + clearTimeout(spinnerTimeout); + spinner.removeClass('active'); + } + }); + } + + function expandBox() { + if (box.data('origin') === 'Search/Search') { + var logoWidth = logo.width(); + $('body').addClass('search-focused'); + logo.css('margin-left', (logoWidth * -1) + 'px'); + + if (!_.str.isBlank(dropBody.text())) { + logo.one(Prefixer.event.transitionEnd, function (e) { + openDrop(); + }); + } + } + } + + function shrinkBox() { + if (box.data('origin') === 'Search/Search') { + $('body').removeClass('search-focused'); + logo.css('margin-left', ''); + } + } + + function openDrop() { + if (!drop.hasClass('open')) { + drop.addClass('open'); + beginKeyEvents(); + } + } + + function closeDrop() { + drop.removeClass('open'); + endKeyEvents(); + } + + function beginKeyEvents() { + if (keyNav) + return; + + // start listening to Down, Up and Enter keys + + dropBody.keyNav({ + exclusiveKeyListener: false, + scrollToKeyHoverItem: false, + selectionItemSelector: ".instasearch-hit", + selectedItemHoverClass: "key-hover", + keyActions: [ + { keyCode: 13, action: "select" }, //enter + { keyCode: 38, action: "up" }, //up + { keyCode: 40, action: "down" }, //down + ] + }); + + keyNav = dropBody.data("keyNav"); + + dropBody.on("keyNav.selected", function (e) { + // Triggered when user presses Enter after navigating to a hit with keyboard + var el = $(e.selectedElement); + var href = el.attr('href') || el.data('href'); + if (href) { + closeDrop(); + location.replace(href); + } + }); + } + + function endKeyEvents() { + if (keyNav) { + dropBody.off("keyNav.selected"); + keyNav.destroy(); + keyNav = null; + } + } + + form.on("submit", function (e) { + if (!box.val()) { + // Shake the form on submit but no term has been entered + var frm = $(this); + var shakeOpts = { direction: "right", distance: 4, times: 2 }; + frm.stop(true, true).effect("shake", shakeOpts, 400, function () { + box.trigger("focus").removeClass("placeholder") + }); + return false; + } + + return true; + }); + }); + }); + + + // + // Facets + // ========================================================== + + $(function () { + var widget = $('#faceted-search'); + if (widget.length === 0) + return; + + // + // Handle facet widget filter events + // ============================================= + (function () { + // Handle checkboxes + widget.on('change', ':input[type=checkbox].facet-control-native', facetControlClickHandler); + + // Handle radio buttons + widget.on('click', ':input[type=radio].facet-control-native', facetControlClickHandler); + + function facetControlClickHandler(e) { + var href = $(this).closest('[data-href]').data('href'); + if (href) { + setLocation(href); + } + } + + // Custom ranges (prices, custom numeric attributes etc.) + widget.on('click', '.btn-custom-range', function (e) { + var btn = $(this), + cnt = btn.closest('.facet-range-container'), + minVal = cnt.find('.facet-range-from').val(), + maxVal = cnt.find('.facet-range-to').val(); + + var expr = minVal.replace(/[^\d\.\-]/g, '') + '~' + maxVal.replace(/[^\d\.\-]/g, ''); + + var url = modifyUrl(null, btn.data('qname'), expr.length > 1 ? expr : null); + setLocation(url); + }); + + // Validate custom range selection + widget.on('change', 'select.facet-range-from, select.facet-range-to', function (e, recursive) { + if (recursive) + return; + + var select = $(this), + isMin = select.hasClass('facet-range-from'), + otherSelect = select.closest('.facet-range-container').find('select.facet-range-' + (isMin ? 'to' : 'from')), + idx = select.find('option:selected').index(), + otherIdx = otherSelect.find('option:selected').index(); + + function validateRangeControls() { + var newIdx = Math.min($('option', otherSelect).length - 1, Math.max(0, isMin ? idx + 1 : idx - 1)); + if (newIdx == idx) { + newIdx = 0; + } + + $('option:eq(' + newIdx + ')', otherSelect).prop('selected', true); + otherSelect.trigger('change', [ true ]); + } + + if (idx > 0 && otherIdx > 0 && ((isMin && idx >= otherIdx) || (!isMin && idx <= otherIdx))) { + validateRangeControls(); + } + }); + + // Switch range value to upper or back. + widget.on('change', 'select.facet-switch-range', function (e, recursive) { + if (recursive) + return; + + var select = $(this), + selectedUrl = null, + toUpper = select.val() === 'upper', + qname = select.data('qname'); + + // Update all url and input values. + select.closest('.facet-group').find('.facet-item').each(function (index) { + var item = $(this), + url = item.attr('data-href'), + input = item.find('input.facet-control-native'), + val = input.val().replace('~', ''); + + if (toUpper) { + val = '~' + val; + } + url = modifyUrl(url, qname, val); + + input.val(val); + item.attr('data-href', url); + + if (input.is(':checked')) { + selectedUrl = url; + } + }); + + // Update location for selected filter. + if (selectedUrl) { + setLocation(selectedUrl); + } + }); + })(); + + + // + // Handle local search + // ============================================= + (function () { + widget.on('input propertychange paste', '.facet-local-search-input', function (e) { + var el = $(this); + + // Retrieve the input field text and reset the count to zero + var filter = el.val(), + rg = new RegExp(filter, "i"); + + // Loop through the facet items + el.closest('.facet-body').find('.facet-item').each(function () { + var item = $(this); + + // If the facet item does not contain the text phrase hide it + if (filter.length > 0 && item.text().search(rg) < 0) { + item.hide(); + } + // Show the facet item if the phrase matches + else { + item.show(); + } + }); + }); + })(); + + + // + // Handle widget responsiveness (offcanvas) + // ============================================= + (function () { + var btn = $('.btn-toggle-filter-widget'); + if (btn.length === 0) + return; + + var viewport = ResponsiveBootstrapToolkit; + + function collapseWidget(afterResize) { + if (btn.data('offcanvas')) return; + + // create offcanvas wrapper + var placement = SmartStore.globalization.culture.isRTL ? 'right' : 'left'; + var offcanvas = $('').appendTo('body'); + + // handle .offcanvas-closer click + offcanvas.one('click', '.offcanvas-closer', function (e) { + offcanvas.offcanvas('hide'); + }); + + // put widget into offcanvas wrapper + widget.appendTo(offcanvas.children().first()); + + btn.data('offcanvas', offcanvas) + .attr('data-toggle', 'offcanvas') + .attr('data-placement', placement) + .attr('data-disablescrolling', 'true') + .data('target', offcanvas); + + if (!afterResize) { + // Collapse all groups on initial page load + widget.find('.facet-toggle:not(.collapsed)').addClass('collapsed'); + widget.find('.facet-body.show').removeClass('show'); + } + } + + function restoreWidget() { + if (!btn.data('offcanvas')) return; + + // move widget back to its origin + var offcanvas = btn.data('offcanvas'); + widget.appendTo($('.faceted-search-container')); + offcanvas.remove(); + + btn.removeData('offcanvas') + .removeAttr('data-toggle') + .removeAttr('data-placement') + .removeAttr('data-disablescrolling') + .removeData('target'); + } + + function toggleOffCanvas(afterResize) { + var breakpoint = '= 112 && k <= 123; - } - }; - - nextUid=(function() { var counter=1; return function() { return counter++; }; }()); - - function indexOf(value, array) { - var i = 0, l = array.length, v; - - if (typeof value === "undefined") { - return -1; - } - - if (value.constructor === String) { - for (; i < l; i = i + 1) if (value.localeCompare(array[i]) === 0) return i; - } else { - for (; i < l; i = i + 1) { - v = array[i]; - if (v.constructor === String) { - if (v.localeCompare(value) === 0) return i; - } else { - if (v === value) return i; - } - } - } - return -1; - } - - /** - * Compares equality of a and b taking into account that a and b may be strings, in which case localeCompare is used - * @param a - * @param b - */ - function equal(a, b) { - if (a === b) return true; - if (a === undefined || b === undefined) return false; - if (a === null || b === null) return false; - if (a.constructor === String) return a.localeCompare(b) === 0; - if (b.constructor === String) return b.localeCompare(a) === 0; - return false; - } - - /** - * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty - * strings - * @param string - * @param separator - */ - function splitVal(string, separator) { - var val, i, l; - if (string === null || string.length < 1) return []; - val = string.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); - return val; - } - - function getSideBorderPadding(element) { - return element.outerWidth() - element.width(); - } - - function installKeyUpChangeEvent(element) { - var key="keyup-change-value"; - element.bind("keydown", function () { - if ($.data(element, key) === undefined) { - $.data(element, key, element.val()); - } - }); - element.bind("keyup", function () { - var val= $.data(element, key); - if (val !== undefined && element.val() !== val) { - $.removeData(element, key); - element.trigger("keyup-change"); - } - }); - } - - $(document).delegate("body", "mousemove", function (e) { - $.data(document, "select2-lastpos", {x: e.pageX, y: e.pageY}); - }); - - /** - * filters mouse events so an event is fired only if the mouse moved. - * - * filters out mouse events that occur when mouse is stationary but - * the elements under the pointer are scrolled. - */ - function installFilteredMouseMove(element) { - element.bind("mousemove", function (e) { - var lastpos = $.data(document, "select2-lastpos"); - if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { - $(e.target).trigger("mousemove-filtered", e); - } - }); - } - - /** - * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made - * within the last quietMillis milliseconds. - * - * @param quietMillis number of milliseconds to wait before invoking fn - * @param fn function to be debounced - * @param ctx object to be used as this reference within fn - * @return debounced version of fn - */ - function debounce(quietMillis, fn, ctx) { - ctx = ctx || undefined; - var timeout; - return function () { - var args = arguments; - window.clearTimeout(timeout); - timeout = window.setTimeout(function() { - fn.apply(ctx, args); - }, quietMillis); - }; - } - - /** - * A simple implementation of a thunk - * @param formula function used to lazily initialize the thunk - * @return {Function} - */ - function thunk(formula) { - var evaluated = false, - value; - return function() { - if (evaluated === false) { value = formula(); evaluated = true; } - return value; - }; - }; - - function installDebouncedScroll(threshold, element) { - var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); - element.bind("scroll", function (e) { - if (indexOf(e.target, element.get()) >= 0) notify(e); - }); - } - - function killEvent(event) { - event.preventDefault(); - event.stopPropagation(); - } - - function measureTextWidth(e) { - if (!sizer){ - var style = e[0].currentStyle || window.getComputedStyle(e[0], null); - sizer = $("
            ").css({ - position: "absolute", - left: "-10000px", - top: "-10000px", - display: "none", - fontSize: style.fontSize, - fontFamily: style.fontFamily, - fontStyle: style.fontStyle, - fontWeight: style.fontWeight, - letterSpacing: style.letterSpacing, - textTransform: style.textTransform, - whiteSpace: "nowrap" - }); - $("body").append(sizer); - } - sizer.text(e.val()); - return sizer.width(); - } - - function markMatch(text, term, markup) { - var match=text.toUpperCase().indexOf(term.toUpperCase()), - tl=term.length; - - if (match<0) { - markup.push(text); - return; - } - - markup.push(text.substring(0, match)); - markup.push(""); - markup.push(text.substring(match, match + tl)); - markup.push(""); - markup.push(text.substring(match + tl, text.length)); - } - - /** - * Produces an ajax-based query function - * - * @param options object containing configuration paramters - * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax - * @param options.url url for the data - * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. - * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified - * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request - * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often - * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2. - * The expected format is an object containing the following keys: - * results array of objects that will be used as choices - * more (optional) boolean indicating whether there are more results available - * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} - */ - function ajax(options) { - var timeout, // current scheduled but not yet executed request - requestSequence = 0, // sequence used to drop out-of-order responses - handler = null, - quietMillis = options.quietMillis || 100; - - return function (query) { - window.clearTimeout(timeout); - timeout = window.setTimeout(function () { - requestSequence += 1; // increment the sequence - var requestNumber = requestSequence, // this request's sequence number - data = options.data, // ajax data function - transport = options.transport || $.ajax, - traditional = options.traditional || false, - type = options.type || 'GET'; // set type of request (GET or POST) - - data = data.call(this, query.term, query.page, query.context); - - if( null !== handler) { handler.abort(); } - - handler = transport.call(null, { - url: options.url, - dataType: options.dataType, - data: data, - type: type, - traditional: traditional, - success: function (data) { - if (requestNumber < requestSequence) { - return; - } - // TODO 3.0 - replace query.page with query so users have access to term, page, etc. - var results = options.results(data, query.page); - query.callback(results); - } - }); - }, quietMillis); - }; - } - - /** - * Produces a query function that works with a local array - * - * @param options object containing configuration parameters. The options parameter can either be an array or an - * object. - * - * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. - * - * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain - * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' - * key can either be a String in which case it is expected that each element in the 'data' array has a key with the - * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract - * the text. - */ - function local(options) { - var data = options, // data elements - dataText, - text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search - - if (!$.isArray(data)) { - text = data.text; - // if text is not a function we assume it to be a key name - if (!$.isFunction(text)) { - dataText = data.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available - text = function (item) { return item[dataText]; }; - } - data = data.results; - } - - return function (query) { - var t = query.term, filtered = { results: [] }, process; - if (t === "") { - query.callback({results: data}); - return; - } - - process = function(datum, collection) { - var group, attr; - datum = datum[0]; - if (datum.children) { - group = {}; - for (attr in datum) { - if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; - } - group.children=[]; - $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); - if (group.children.length) { - collection.push(group); - } - } else { - if (query.matcher(t, text(datum))) { - collection.push(datum); - } - } - }; - - $(data).each2(function(i, datum) { process(datum, filtered.results); }); - query.callback(filtered); - }; - } - - // TODO javadoc - function tags(data) { - // TODO even for a function we should probably return a wrapper that does the same object/string check as - // the function for arrays. otherwise only functions that return objects are supported. - if ($.isFunction(data)) { - return data; - } - - // if not a function we assume it to be an array - - return function (query) { - var t = query.term, filtered = {results: []}; - $(data).each(function () { - var isObject = this.text !== undefined, - text = isObject ? this.text : this; - if (t === "" || query.matcher(t, text)) { - filtered.results.push(isObject ? this : {id: this, text: this}); - } - }); - query.callback(filtered); - }; - } - - /** - * Checks if the formatter function should be used. - * - * Throws an error if it is not a function. Returns true if it should be used, - * false if no formatting should be performed. - * - * @param formatter - */ - function checkFormatter(formatter, formatterName) { - if ($.isFunction(formatter)) return true; - if (!formatter) return false; - throw new Error("formatterName must be a function or a falsy value"); - } - - function evaluate(val) { - return $.isFunction(val) ? val() : val; - } - - function countResults(results) { - var count = 0; - $.each(results, function(i, item) { - if (item.children) { - count += countResults(item.children); - } else { - count++; - } - }); - return count; - } - - /** - * Default tokenizer. This function uses breaks the input on substring match of any string from the - * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those - * two options have to be defined in order for the tokenizer to work. - * - * @param input text user has typed so far or pasted into the search field - * @param selection currently selected choices - * @param selectCallback function(choice) callback tho add the choice to selection - * @param opts select2's opts - * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value - */ - function defaultTokenizer(input, selection, selectCallback, opts) { - var original = input, // store the original so we can compare and know if we need to tell the search to update its text - dupe = false, // check for whether a token we extracted represents a duplicate selected choice - token, // token - index, // position at which the separator was found - i, l, // looping variables - separator; // the matched separator - - if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; - - while (true) { - index = -1; - - for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { - separator = opts.tokenSeparators[i]; - index = input.indexOf(separator); - if (index >= 0) break; - } - - if (index < 0) break; // did not find any token separator in the input string, bail - - token = input.substring(0, index); - input = input.substring(index + separator.length); - - if (token.length > 0) { - token = opts.createSearchChoice(token, selection); - if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { - dupe = false; - for (i = 0, l = selection.length; i < l; i++) { - if (equal(opts.id(token), opts.id(selection[i]))) { - dupe = true; break; - } - } - - if (!dupe) selectCallback(token); - } - } - } - - if (original.localeCompare(input) != 0) return input; - } - - /** - * blurs any Select2 container that has focus when an element outside them was clicked or received focus - * - * also takes care of clicks on label tags that point to the source element - */ - $(document).ready(function () { - $(document).delegate("body", "mousedown touchend", function (e) { - var target = $(e.target).closest("div.select2-container").get(0), attr; - if (target) { - $(document).find("div.select2-container-active").each(function () { - if (this !== target) $(this).data("select2").blur(); - }); - } else { - target = $(e.target).closest("div.select2-drop").get(0); - $(document).find("div.select2-drop-active").each(function () { - if (this !== target) $(this).data("select2").blur(); - }); - } - - target=$(e.target); - attr = target.attr("for"); - if ("LABEL" === e.target.tagName && attr && attr.length > 0) { - target = $("#"+attr); - target = target.data("select2"); - if (target !== undefined) { target.focus(); e.preventDefault();} - } - }); - }); - - /** - * Creates a new class - * - * @param superClass - * @param methods - */ - function clazz(SuperClass, methods) { - var constructor = function () {}; - constructor.prototype = new SuperClass; - constructor.prototype.constructor = constructor; - constructor.prototype.parent = SuperClass.prototype; - constructor.prototype = $.extend(constructor.prototype, methods); - return constructor; - } - - AbstractSelect2 = clazz(Object, { - - // abstract - bind: function (func) { - var self = this; - return function () { - func.apply(self, arguments); - }; - }, - - // abstract - init: function (opts) { - var results, search, resultsSelector = ".select2-results"; - - // prepare options - this.opts = opts = this.prepareOpts(opts); - - this.id=opts.id; - - // destroy if called on an existing component - if (opts.element.data("select2") !== undefined && - opts.element.data("select2") !== null) { - this.destroy(); - } - - this.enabled=true; - this.container = this.createContainer(); - - this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); - this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); - this.container.attr("id", this.containerId); - - // cache the body so future lookups are cheap - this.body = thunk(function() { return opts.element.closest("body"); }); - - if (opts.element.attr("class") !== undefined) { - this.container.addClass(opts.element.attr("class").replace(/validate\[[\S ]+] ?/, '')); - } - - this.container.css(evaluate(opts.containerCss)); - this.container.addClass(evaluate(opts.containerCssClass)); - - // swap container for the element - this.opts.element - .data("select2", this) - .hide() - .before(this.container); - this.container.data("select2", this); - - this.dropdown = this.container.find(".select2-drop"); - this.dropdown.addClass(evaluate(opts.dropdownCssClass)); - this.dropdown.data("select2", this); - - this.results = results = this.container.find(resultsSelector); - this.search = search = this.container.find("input.select2-input"); - - search.attr("tabIndex", this.opts.element.attr("tabIndex")); - - this.resultsPage = 0; - this.context = null; - - // initialize the container - this.initContainer(); - this.initContainerWidth(); - - installFilteredMouseMove(this.results); - this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); - - installDebouncedScroll(80, this.results); - this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); - - // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel - if ($.fn.mousewheel) { - results.mousewheel(function (e, delta, deltaX, deltaY) { - var top = results.scrollTop(), height; - if (deltaY > 0 && top - deltaY <= 0) { - results.scrollTop(0); - killEvent(e); - } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { - results.scrollTop(results.get(0).scrollHeight - results.height()); - killEvent(e); - } - }); - } - - installKeyUpChangeEvent(search); - search.bind("keyup-change", this.bind(this.updateResults)); - search.bind("focus", function () { search.addClass("select2-focused"); if (search.val() === " ") search.val(""); }); - search.bind("blur", function () { search.removeClass("select2-focused");}); - - this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) { - if ($(e.target).closest(".select2-result-selectable:not(.select2-disabled)").length > 0) { - this.highlightUnderEvent(e); - this.selectHighlighted(e); - } else { - this.focusSearch(); - } - killEvent(e); - })); - - // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening - // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's - // dom it will trigger the popup close, which is not what we want - this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); }); - - if ($.isFunction(this.opts.initSelection)) { - // initialize selection based on the current value of the source element - this.initSelection(); - - // if the user has provided a function that can set selection based on the value of the source element - // we monitor the change event on the element and trigger it, allowing for two way synchronization - this.monitorSource(); - } - - if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable(); - }, - - // abstract - destroy: function () { - var select2 = this.opts.element.data("select2"); - if (select2 !== undefined) { - select2.container.remove(); - select2.dropdown.remove(); - select2.opts.element - .removeData("select2") - .unbind(".select2") - .show(); - } - }, - - // abstract - prepareOpts: function (opts) { - var element, select, idKey, ajaxUrl; - - element = opts.element; - - if (element.get(0).tagName.toLowerCase() === "select") { - this.select = select = opts.element; - } - - if (select) { - // these options are not allowed when attached to a select because they are picked up off the element itself - $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { - if (this in opts) { - throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a " , - "
          • " , - "
              " , - "
            " , - "
            "].join("")); - return container; - }, - - // single - opening: function () { - this.search.show(); - this.parent.opening.apply(this, arguments); - this.dropdown.removeClass("select2-offscreen"); - }, - - // single - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show(); - }, - - // single - focus: function () { - this.close(); - this.selection.focus(); - }, - - // single - isFocused: function () { - return this.selection[0] === document.activeElement; - }, - - // single - cancel: function () { - this.parent.cancel.apply(this, arguments); - this.selection.focus(); - }, - - // single - initContainer: function () { - - var selection, - container = this.container, - dropdown = this.dropdown, - clickingInside = false; - - this.selection = selection = container.find(".select2-choice"); - - this.search.bind("keydown", this.bind(function (e) { - if (!this.enabled) return; - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - return; - } - - if (this.opened()) { - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.TAB: - case KEY.ENTER: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - } else { - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { - return; - } - - if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { - return; - } - - this.open(); - - if (e.which === KEY.ENTER) { - // do not propagate the event otherwise we open, and propagate enter which closes - return; - } - } - })); - - this.search.bind("focus", this.bind(function() { - this.selection.attr("tabIndex", "-1"); - })); - this.search.bind("blur", this.bind(function() { - if (!this.opened()) this.container.removeClass("select2-container-active"); - window.setTimeout(this.bind(function() { this.selection.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10); - })); - - selection.bind("mousedown", this.bind(function (e) { - clickingInside = true; - - if (this.opened()) { - this.close(); - this.selection.focus(); - } else if (this.enabled) { - this.open(); - } - - clickingInside = false; - })); - - dropdown.bind("mousedown", this.bind(function() { this.search.focus(); })); - - selection.bind("focus", this.bind(function() { - this.container.addClass("select2-container-active"); - // hide the search so the tab key does not focus on it - this.search.attr("tabIndex", "-1"); - })); - - selection.bind("blur", this.bind(function() { - if (!this.opened()) { - this.container.removeClass("select2-container-active"); - } - window.setTimeout(this.bind(function() { this.search.attr("tabIndex", this.opts.element.attr("tabIndex")); }), 10); - })); - - selection.bind("keydown", this.bind(function(e) { - if (!this.enabled) return; - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - return; - } - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) - || e.which === KEY.ESC) { - return; - } - - if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { - return; - } - - if (e.which == KEY.DELETE) { - if (this.opts.allowClear) { - this.clear(); - } - return; - } - - this.open(); - - if (e.which === KEY.ENTER) { - // do not propagate the event otherwise we open, and propagate enter which closes - killEvent(e); - return; - } - - // do not set the search input value for non-alpha-numeric keys - // otherwise pressing down results in a '(' being set in the search field - if (e.which < 48 ) { // '0' == 48 - killEvent(e); - return; - } - - var keyWritten = String.fromCharCode(e.which).toLowerCase(); - - if (e.shiftKey) { - keyWritten = keyWritten.toUpperCase(); - } - - // focus the field before calling val so the cursor ends up after the value instead of before - this.search.focus(); - this.search.val(keyWritten); - - // prevent event propagation so it doesnt replay on the now focussed search field and result in double key entry - killEvent(e); - })); - - selection.delegate("abbr", "mousedown", this.bind(function (e) { - if (!this.enabled) return; - this.clear(); - killEvent(e); - this.close(); - this.triggerChange(); - this.selection.focus(); - })); - - this.setPlaceholder(); - - this.search.bind("focus", this.bind(function() { - this.container.addClass("select2-container-active"); - })); - }, - - // single - clear: function() { - this.opts.element.val(""); - this.selection.find("span").empty(); - this.selection.removeData("select2-data"); - this.setPlaceholder(); - }, - - /** - * Sets selection based on source element's value - */ - // single - initSelection: function () { - var selected; - if (this.opts.element.val() === "") { - this.close(); - this.setPlaceholder(); - } else { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(selected){ - if (selected !== undefined && selected !== null) { - self.updateSelection(selected); - self.close(); - self.setPlaceholder(); - } - }); - } - }, - - // single - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install the selection initializer - opts.initSelection = function (element, callback) { - var selected = element.find(":selected"); - // a single select box always has a value, no need to null check 'selected' - if ($.isFunction(callback)) - callback({id: selected.attr("value"), text: selected.text()}); - }; - } - - return opts; - }, - - // single - setPlaceholder: function () { - var placeholder = this.getPlaceholder(); - - if (this.opts.element.val() === "" && placeholder !== undefined) { - - // check for a first blank option if attached to a select - if (this.select && this.select.find("option:first").text() !== "") return; - - this.selection.find("span").html(this.opts.escapeMarkup(placeholder)); - - this.selection.addClass("select2-default"); - - this.selection.find("abbr").hide(); - } - }, - - // single - postprocessResults: function (data, initial) { - var selected = 0, self = this, showSearchInput = true; - - // find the selected element in the result list - - this.results.find(".select2-result-selectable").each2(function (i, elm) { - if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { - selected = i; - return false; - } - }); - - // and highlight it - - this.highlight(selected); - - // hide the search box if this is the first we got the results and there are a few of them - - if (initial === true) { - showSearchInput = this.showSearchInput = countResults(data.results) >= this.opts.minimumResultsForSearch; - this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden"); - - //add "select2-with-searchbox" to the container if search box is shown - $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox"); - } - - }, - - // single - onSelect: function (data) { - var old = this.opts.element.val(); - - this.opts.element.val(this.id(data)); - this.updateSelection(data); - this.close(); - this.selection.focus(); - - if (!equal(old, this.id(data))) { this.triggerChange(); } - }, - - // single - updateSelection: function (data) { - - var container=this.selection.find("span"), formatted; - - this.selection.data("select2-data", data); - - container.empty(); - formatted=this.opts.formatSelection(data, container); - if (formatted !== undefined) { - container.append(this.opts.escapeMarkup(formatted)); - } - - this.selection.removeClass("select2-default"); - - if (this.opts.allowClear && this.getPlaceholder() !== undefined) { - this.selection.find("abbr").show(); - } - }, - - // single - val: function () { - var val, data = null, self = this; - - if (arguments.length === 0) { - return this.opts.element.val(); - } - - val = arguments[0]; - - if (this.select) { - this.select - .val(val) - .find(":selected").each2(function (i, elm) { - data = {id: elm.attr("value"), text: elm.text()}; - return false; - }); - this.updateSelection(data); - this.setPlaceholder(); - } else { - if (this.opts.initSelection === undefined) { - throw new Error("cannot call val() if initSelection() is not defined"); - } - // val is an id. !val is true for [undefined,null,''] - if (!val) { - this.clear(); - return; - } - this.opts.element.val(val); - this.opts.initSelection(this.opts.element, function(data){ - self.opts.element.val(!data ? "" : self.id(data)); - self.updateSelection(data); - self.setPlaceholder(); - }); - } - }, - - // single - clearSearch: function () { - this.search.val(""); - }, - - // single - data: function(value) { - var data; - - if (arguments.length === 0) { - data = this.selection.data("select2-data"); - if (data == undefined) data = null; - return data; - } else { - if (!value || value === "") { - this.clear(); - } else { - this.opts.element.val(!value ? "" : this.id(value)); - this.updateSelection(value); - } - } - } - }); - - MultiSelect2 = clazz(AbstractSelect2, { - - // multi - createContainer: function () { - var container = $("
            ", { - "class": "select2-container select2-container-multi" - }).html([ - "
              ", - //"
            • California
            • " , - "
            • " , - " " , - "
            • " , - "
            " , - ""].join("")); - return container; - }, - - // multi - prepareOpts: function () { - var opts = this.parent.prepareOpts.apply(this, arguments); - - // TODO validate placeholder is a string if specified - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install sthe selection initializer - opts.initSelection = function (element,callback) { - - var data = []; - element.find(":selected").each2(function (i, elm) { - data.push({id: elm.attr("value"), text: elm.text()}); - }); - - if ($.isFunction(callback)) - callback(data); - }; - } - - return opts; - }, - - // multi - initContainer: function () { - - var selector = ".select2-choices", selection; - - this.searchContainer = this.container.find(".select2-search-field"); - this.selection = selection = this.container.find(selector); - - this.search.bind("keydown", this.bind(function (e) { - if (!this.enabled) return; - - if (e.which === KEY.BACKSPACE && this.search.val() === "") { - this.close(); - - var choices, - selected = selection.find(".select2-search-choice-focus"); - if (selected.length > 0) { - this.unselect(selected.first()); - this.search.width(10); - killEvent(e); - return; - } - - choices = selection.find(".select2-search-choice"); - if (choices.length > 0) { - choices.last().addClass("select2-search-choice-focus"); - } - } else { - selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - } - - if (this.opened()) { - switch (e.which) { - case KEY.UP: - case KEY.DOWN: - this.moveHighlight((e.which === KEY.UP) ? -1 : 1); - killEvent(e); - return; - case KEY.ENTER: - case KEY.TAB: - this.selectHighlighted(); - killEvent(e); - return; - case KEY.ESC: - this.cancel(e); - killEvent(e); - return; - } - } - - if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) - || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { - return; - } - - if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { - return; - } - - this.open(); - - if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { - // prevent the page from scrolling - killEvent(e); - } - })); - - this.search.bind("keyup", this.bind(this.resizeSearch)); - - this.search.bind("blur", this.bind(function(e) { - this.container.removeClass("select2-container-active"); - this.search.removeClass("select2-focused"); - this.clearSearch(); - e.stopImmediatePropagation(); - })); - - this.container.delegate(selector, "mousedown", this.bind(function (e) { - if (!this.enabled) return; - if ($(e.target).closest(".select2-search-choice").length > 0) { - // clicked inside a select2 search choice, do not open - return; - } - this.clearPlaceholder(); - this.open(); - this.focusSearch(); - e.preventDefault(); - })); - - this.container.delegate(selector, "focus", this.bind(function () { - if (!this.enabled) return; - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - this.clearPlaceholder(); - })); - - // set the placeholder if necessary - this.clearSearch(); - }, - - // multi - enable: function() { - if (this.enabled) return; - - this.parent.enable.apply(this, arguments); - - this.search.removeAttr("disabled"); - }, - - // multi - disable: function() { - if (!this.enabled) return; - - this.parent.disable.apply(this, arguments); - - this.search.attr("disabled", true); - }, - - // multi - initSelection: function () { - var data; - if (this.opts.element.val() === "") { - this.updateSelection([]); - this.close(); - // set the placeholder if necessary - this.clearSearch(); - } - if (this.select || this.opts.element.val() !== "") { - var self = this; - this.opts.initSelection.call(null, this.opts.element, function(data){ - if (data !== undefined && data !== null) { - self.updateSelection(data); - self.close(); - // set the placeholder if necessary - self.clearSearch(); - } - }); - } - }, - - // multi - clearSearch: function () { - var placeholder = this.getPlaceholder(); - - if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { - this.search.val(placeholder).addClass("select2-default"); - // stretch the search box to full width of the container so as much of the placeholder is visible as possible - this.resizeSearch(); - } else { - // we set this to " " instead of "" and later clear it on focus() because there is a firefox bug - // that does not properly render the caret when the field starts out blank - this.search.val(" ").width(10); - } - }, - - // multi - clearPlaceholder: function () { - if (this.search.hasClass("select2-default")) { - this.search.val("").removeClass("select2-default"); - } else { - // work around for the space character we set to avoid firefox caret bug - if (this.search.val() === " ") this.search.val(""); - } - }, - - // multi - opening: function () { - this.parent.opening.apply(this, arguments); - - this.clearPlaceholder(); - this.resizeSearch(); - this.focusSearch(); - }, - - // multi - close: function () { - if (!this.opened()) return; - this.parent.close.apply(this, arguments); - }, - - // multi - focus: function () { - this.close(); - this.search.focus(); - }, - - // multi - isFocused: function () { - return this.search.hasClass("select2-focused"); - }, - - // multi - updateSelection: function (data) { - var ids = [], filtered = [], self = this; - - // filter out duplicates - $(data).each(function () { - if (indexOf(self.id(this), ids) < 0) { - ids.push(self.id(this)); - filtered.push(this); - } - }); - data = filtered; - - this.selection.find(".select2-search-choice").remove(); - $(data).each(function () { - self.addSelectedChoice(this); - }); - self.postprocessResults(); - }, - - tokenize: function() { - var input = this.search.val(); - input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts); - if (input != null && input != undefined) { - this.search.val(input); - if (input.length > 0) { - this.open(); - } - } - - }, - - // multi - onSelect: function (data) { - this.addSelectedChoice(data); - if (this.select) { this.postprocessResults(); } - - if (this.opts.closeOnSelect) { - this.close(); - this.search.width(10); - } else { - if (this.countSelectableResults()>0) { - this.search.width(10); - this.resizeSearch(); - this.positionDropdown(); - } else { - // if nothing left to select close - this.close(); - } - } - - // since its not possible to select an element that has already been - // added we do not need to check if this is a new element before firing change - this.triggerChange({ added: data }); - - this.focusSearch(); - }, - - // multi - cancel: function () { - this.close(); - this.focusSearch(); - }, - - // multi - addSelectedChoice: function (data) { - var choice=$( - "
          • " + - "
            " + - " " + - "
          • "), - id = this.id(data), - val = this.getVal(), - formatted; - - formatted=this.opts.formatSelection(data, choice); - choice.find("div").replaceWith("
            "+this.opts.escapeMarkup(formatted)+"
            "); - choice.find(".select2-search-choice-close") - .bind("mousedown", killEvent) - .bind("click dblclick", this.bind(function (e) { - if (!this.enabled) return; - - $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){ - this.unselect($(e.target)); - this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); - this.close(); - this.focusSearch(); - })).dequeue(); - killEvent(e); - })).bind("focus", this.bind(function () { - if (!this.enabled) return; - this.container.addClass("select2-container-active"); - this.dropdown.addClass("select2-drop-active"); - })); - - choice.data("select2-data", data); - choice.insertBefore(this.searchContainer); - - val.push(id); - this.setVal(val); - }, - - // multi - unselect: function (selected) { - var val = this.getVal(), - data, - index; - - selected = selected.closest(".select2-search-choice"); - - if (selected.length === 0) { - throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; - } - - data = selected.data("select2-data"); - - index = indexOf(this.id(data), val); - - if (index >= 0) { - val.splice(index, 1); - this.setVal(val); - if (this.select) this.postprocessResults(); - } - selected.remove(); - this.triggerChange({ removed: data }); - }, - - // multi - postprocessResults: function () { - var val = this.getVal(), - choices = this.results.find(".select2-result-selectable"), - compound = this.results.find(".select2-result-with-children"), - self = this; - - choices.each2(function (i, choice) { - var id = self.id(choice.data("select2-data")); - if (indexOf(id, val) >= 0) { - choice.addClass("select2-disabled").removeClass("select2-result-selectable"); - } else { - choice.removeClass("select2-disabled").addClass("select2-result-selectable"); - } - }); - - compound.each2(function(i, e) { - if (e.find(".select2-result-selectable").length==0) { - e.addClass("select2-disabled"); - } else { - e.removeClass("select2-disabled"); - } - }); - - choices.each2(function (i, choice) { - if (!choice.hasClass("select2-disabled") && choice.hasClass("select2-result-selectable")) { - self.highlight(0); - return false; - } - }); - - }, - - // multi - resizeSearch: function () { - - var minimumWidth, left, maxWidth, containerLeft, searchWidth, - sideBorderPadding = getSideBorderPadding(this.search); - - minimumWidth = measureTextWidth(this.search) + 10; - - left = this.search.offset().left; - - maxWidth = this.selection.width(); - containerLeft = this.selection.offset().left; - - searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; - if (searchWidth < minimumWidth) { - searchWidth = maxWidth - sideBorderPadding; - } - - if (searchWidth < 40) { - searchWidth = maxWidth - sideBorderPadding; - } - this.search.width(searchWidth); - }, - - // multi - getVal: function () { - var val; - if (this.select) { - val = this.select.val(); - return val === null ? [] : val; - } else { - val = this.opts.element.val(); - return splitVal(val, this.opts.separator); - } - }, - - // multi - setVal: function (val) { - var unique; - if (this.select) { - this.select.val(val); - } else { - unique = []; - // filter out duplicates - $(val).each(function () { - if (indexOf(this, unique) < 0) unique.push(this); - }); - this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); - } - }, - - // multi - val: function () { - var val, data = [], self=this; - - if (arguments.length === 0) { - return this.getVal(); - } - - val = arguments[0]; - - if (!val) { - this.opts.element.val(""); - this.updateSelection([]); - this.clearSearch(); - return; - } - - // val is a list of ids - this.setVal(val); - - if (this.select) { - this.select.find(":selected").each(function () { - data.push({id: $(this).attr("value"), text: $(this).text()}); - }); - this.updateSelection(data); - } else { - if (this.opts.initSelection === undefined) { - throw new Error("val() cannot be called if initSelection() is not defined") - } - - this.opts.initSelection(this.opts.element, function(data){ - var ids=$(data).map(self.id); - self.setVal(ids); - self.updateSelection(data); - self.clearSearch(); - }); - } - this.clearSearch(); - }, - - // multi - onSortStart: function() { - if (this.select) { - throw new Error("Sorting of elements is not supported when attached to instead."); - } - - // collapse search field into 0 width so its container can be collapsed as well - this.search.width(0); - // hide the container - this.searchContainer.hide(); - }, - - // multi - onSortEnd:function() { - - var val=[], self=this; - - // show search and move it to the end of the list - this.searchContainer.show(); - // make sure the search container is the last item in the list - this.searchContainer.appendTo(this.searchContainer.parent()); - // since we collapsed the width in dragStarted, we resize it here - this.resizeSearch(); - - // update selection - - this.selection.find(".select2-search-choice").each(function() { - val.push(self.opts.id($(this).data("select2-data"))); - }); - this.setVal(val); - this.triggerChange(); - }, - - // multi - data: function(values) { - var self=this, ids; - if (arguments.length === 0) { - return this.selection - .find(".select2-search-choice") - .map(function() { return $(this).data("select2-data"); }) - .get(); - } else { - if (!values) { values = []; } - ids = $.map(values, function(e) { return self.opts.id(e)}); - this.setVal(ids); - this.updateSelection(values); - this.clearSearch(); - } - } - }); - - $.fn.select2 = function () { - - var args = Array.prototype.slice.call(arguments, 0), - opts, - select2, - value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data"]; - - this.each(function () { - if (args.length === 0 || typeof(args[0]) === "object") { - opts = args.length === 0 ? {} : $.extend({}, args[0]); - opts.element = $(this); - - if (opts.element.get(0).tagName.toLowerCase() === "select") { - multiple = opts.element.attr("multiple"); - } else { - multiple = opts.multiple || false; - if ("tags" in opts) {opts.multiple = multiple = true;} - } - - select2 = multiple ? new MultiSelect2() : new SingleSelect2(); - select2.init(opts); - } else if (typeof(args[0]) === "string") { - - if (indexOf(args[0], allowedMethods) < 0) { - throw "Unknown method: " + args[0]; - } - - value = undefined; - select2 = $(this).data("select2"); - if (select2 === undefined) return; - if (args[0] === "container") { - value=select2.container; - } else { - value = select2[args[0]].apply(select2, args.slice(1)); - } - if (value !== undefined) {return false;} - } else { - throw "Invalid arguments to select2 plugin: " + args; - } - }); - return (value === undefined) ? this : value; - }; - - // plugin defaults, accessible to users - $.fn.select2.defaults = { - width: "copy", - closeOnSelect: true, - openOnEnter: true, - containerCss: {}, - dropdownCss: {}, - containerCssClass: "", - dropdownCssClass: "", - formatResult: function(result, container, query) { - var markup=[]; - markMatch(result.text, query.term, markup); - return markup.join(""); - }, - formatSelection: function (data, container) { - return data ? data.text : undefined; - }, - formatResultCssClass: function(data) {return undefined;}, - formatNoMatches: function () { return "No matches found"; }, - formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; }, - formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, - formatLoadMore: function (pageNumber) { return "Loading more results..."; }, - formatSearching: function () { return "Searching..."; }, - minimumResultsForSearch: 0, - minimumInputLength: 0, - maximumSelectionSize: 0, - id: function (e) { return e.id; }, - matcher: function(term, text) { - return text.toUpperCase().indexOf(term.toUpperCase()) >= 0; - }, - separator: ",", - tokenSeparators: [], - tokenizer: defaultTokenizer, - escapeMarkup: function (markup) { - if (markup && typeof(markup) === "string") { - return markup.replace(/&/g, "&"); - } - return markup; - }, - blurOnChange: false - }; - - // exports - window.Select2 = { - query: { - ajax: ajax, - local: local, - tags: tags - }, util: { - debounce: debounce, - markMatch: markMatch - }, "class": { - "abstract": AbstractSelect2, - "single": SingleSelect2, - "multi": MultiSelect2 - } - }; - -}(jQuery)); diff --git a/src/Presentation/SmartStore.Web/Scripts/select2.min.js b/src/Presentation/SmartStore.Web/Scripts/select2.min.js deleted file mode 100644 index 1523735658..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/select2.min.js +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2012 Igor Vaynberg - -Version: 3.2 Timestamp: Mon Sep 10 10:38:04 PDT 2012 - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in -compliance with the License. You may obtain a copy of the License in the LICENSE file, or 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(e){"undefined"==typeof e.fn.each2&&e.fn.extend({each2:function(g){for(var i=e([0]),m=-1,s=this.length;++ma.length)return[]; -c=a.split(b);d=0;for(j=c.length;dd?c.push(a):(c.push(a.substring(0,d)),c.push(""),c.push(a.substring(d,d+b)),c.push(""),c.push(a.substring(d+b,a.length)))}function C(a){var b, -c=0,d=null,j=a.quietMillis||100;return function(h){window.clearTimeout(b);b=window.setTimeout(function(){var b=c+=1,j=a.data,n=a.transport||e.ajax,f=a.traditional||!1,g=a.type||"GET",j=j.call(this,h.term,h.page,h.context);null!==d&&d.abort();d=n.call(null,{url:a.url,dataType:a.dataType,data:j,type:g,traditional:f,success:function(d){bd.tokenSeparators.length)return g;for(;;){h=-1;k=0; -for(n=d.tokenSeparators.length;kh)break;f=a.substring(0,h);a=a.substring(h+o.length);if(0=a}};var I=1;G=function(){return I++}; -e(document).delegate("body","mousemove",function(a){e.data(document,"select2-lastpos",{x:a.pageX,y:a.pageY})});e(document).ready(function(){e(document).delegate("body","mousedown touchend",function(a){var b=e(a.target).closest("div.select2-container").get(0),c;b?e(document).find("div.select2-container-active").each(function(){this!==b&&e(this).data("select2").blur()}):(b=e(a.target).closest("div.select2-drop").get(0),e(document).find("div.select2-drop-active").each(function(){this!==b&&e(this).data("select2").blur()})); -b=e(a.target);c=b.attr("for");"LABEL"===a.target.tagName&&(c&&0\|])/g,"\\$1");this.container.attr("id",this.containerId);var d=!1,j;this.body=function(){!1===d&&(j=a.element.closest("body"),d=!0);return j};a.element.attr("class")!==g&&this.container.addClass(a.element.attr("class").replace(/validate\[[\S ]+] ?/,""));this.container.css(v(a.containerCss));this.container.addClass(v(a.containerCssClass));this.opts.element.data("select2",this).hide().before(this.container);this.container.data("select2", -this);this.dropdown=this.container.find(".select2-drop");this.dropdown.addClass(v(a.dropdownCssClass));this.dropdown.data("select2",this);this.results=b=this.container.find(".select2-results");this.search=c=this.container.find("input.select2-input");c.attr("tabIndex",this.opts.element.attr("tabIndex"));this.resultsPage=0;this.context=null;this.initContainer();this.initContainerWidth();this.results.bind("mousemove",function(a){var b=e.data(document,"select2-lastpos");(b===g||b.x!==a.pageX||b.y!==a.pageY)&& -e(a.target).trigger("mousemove-filtered",a)});this.dropdown.delegate(".select2-results","mousemove-filtered",this.bind(this.highlightUnderEvent));var h=this.results,f=A(80,function(a){h.trigger("scroll-debounced",a)});h.bind("scroll",function(a){0<=i(a.target,h.get())&&f(a)});this.dropdown.delegate(".select2-results","scroll-debounced",this.bind(this.loadMoreIfNeeded));e.fn.mousewheel&&b.mousewheel(function(a,c,d,e){c=b.scrollTop();0=c-e?(b.scrollTop(0),l(a)):0>e&&b.get(0).scrollHeight-b.scrollTop()+ -e<=b.height()&&(b.scrollTop(b.get(0).scrollHeight-b.height()),l(a))});c.bind("keydown",function(){e.data(c,"keyup-change-value")===g&&e.data(c,"keyup-change-value",c.val())});c.bind("keyup",function(){var a=e.data(c,"keyup-change-value");a!==g&&c.val()!==a&&(e.removeData(c,"keyup-change-value"),c.trigger("keyup-change"))});c.bind("keyup-change",this.bind(this.updateResults));c.bind("focus",function(){c.addClass("select2-focused");" "===c.val()&&c.val("")});c.bind("blur",function(){c.removeClass("select2-focused")}); -this.dropdown.delegate(".select2-results","mouseup",this.bind(function(a){0 element.");});a=e.extend({},{populateResults:function(b, -c,d){var f,n=this.opts.id,o=this;f=function(b,c,j){var h,l,i,m,r,p,q;h=0;for(l=b.length;h0;p=e("
          • ");p.addClass("select2-results-dept-"+j);p.addClass("select2-result");p.addClass(m?"select2-result-selectable":"select2-result-unselectable");r&&p.addClass("select2-result-with-children");p.addClass(o.opts.formatResultCssClass(i));m=e("
            ");m.addClass("select2-result-label");q=a.formatResult(i,m,d);q!==g&&m.html(o.opts.escapeMarkup(q)); -p.append(m);if(r){r=e("
              ");r.addClass("select2-result-sub");f(i.children,r,j+1);p.append(r)}p.data("select2-data",i);c.append(p)}};f(c,b,0)}},e.fn.select2.defaults,a);"function"!==typeof a.id&&(d=a.id,a.id=function(a){return a[d]});if(c)a.query=this.bind(function(a){var c={results:[],more:false},d=a.term,f,n,o;o=function(b,c){var e;if(b.is("option"))a.matcher(d,b.text(),b)&&c.push({id:b.attr("value"),text:b.text(),element:b.get(),css:b.attr("class")});else if(b.is("optgroup")){e={text:b.attr("label"), -children:[],element:b.get(),css:b.attr("class")};b.children().each2(function(a,b){o(b,e.children)});e.children.length>0&&c.push(e)}};f=b.children();if(this.getPlaceholder()!==g&&f.length>0){n=f[0];e(n).text()===""&&(f=f.not(n))}f.each2(function(a,b){o(b,c.results)});a.callback(c)}),a.id=function(a){return a.id},a.formatResultCssClass=function(a){return a.css};else if(!("query"in a))if("ajax"in a){if((c=a.element.data("ajax-url"))&&0=this.body().scrollTop(),k=this.dropdown.hasClass("select2-drop-above"),n;"static"!==this.body().css("position")&& -(n=this.body().offset(),b-=n.top,f-=n.left);k?(k=!0,!g&&j&&(k=!1)):(k=!1,!j&&g&&(k=!0));k?(b=a.top-d,this.container.addClass("select2-drop-above"),this.dropdown.addClass("select2-drop-above")):(this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above"));a=e.extend({top:b,left:f,width:c},v(this.opts.dropdownCss));this.dropdown.css(a)},shouldOpen:function(){var a;if(this.opened())return!1;a=e.Event("open");this.opts.element.trigger(a);return!a.isDefaultPrevented()}, -clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above");this.dropdown.removeClass("select2-drop-above")},open:function(){if(!this.shouldOpen())return!1;window.setTimeout(this.bind(this.opening),1);return!0},opening:function(){var a=this.containerId,b=this.containerSelector,c="scroll."+a,d="resize."+a;this.container.parents().each(function(){e(this).bind(c,function(){var a=e(b);0==a.length&&e(this).unbind(c);a.select2("close")})});e(window).bind(d,function(){var a= -e(b);0==a.length&&e(window).unbind(d);a.select2("close")});this.clearDropdownAlignmentPreference();" "===this.search.val()&&this.search.val("");this.container.addClass("select2-dropdown-open").addClass("select2-container-active");this.updateResults(!0);this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body());this.dropdown.show();this.positionDropdown();this.dropdown.addClass("select2-drop-active");this.ensureHighlightVisible();this.focusSearch()},close:function(){if(this.opened()){var a= -this;this.container.parents().each(function(){e(this).unbind("scroll."+a.containerId)});e(window).unbind("resize."+this.containerId);this.clearDropdownAlignmentPreference();this.dropdown.hide();this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active");this.results.empty();this.clearSearch();this.opts.element.trigger(e.Event("close"))}},clearSearch:function(){},ensureHighlightVisible:function(){var a=this.results,b,c,d,f;c=this.highlight();0>c||(0==c?a.scrollTop(0): -(b=a.find(".select2-result-selectable"),d=e(b[c]),f=d.offset().top+d.outerHeight(),c===b.length-1&&(b=a.find("li.select2-more-results"),0b&&a.scrollTop(a.scrollTop()+(f-b)),d=d.offset().top-a.offset().top,0>d&&a.scrollTop(a.scrollTop()+d)))},moveHighlight:function(a){for(var b=this.results.find(".select2-result-selectable"),c=this.highlight();-1=b.length&&(a=b.length-1);0>a&&(a=0);b.removeClass("select2-highlighted");e(b[a]).addClass("select2-highlighted");this.ensureHighlightVisible()},countSelectableResults:function(){return this.results.find(".select2-result-selectable").not(".select2-disabled").length}, -highlightUnderEvent:function(a){a=e(a.target).closest(".select2-result-selectable");if(0=c&&(b.addClass("select2-active"),this.opts.query({term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),!0===c.more?(b.detach().appendTo(a).text(e.opts.formatLoadMore(d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d)})})))},tokenize:function(){},updateResults:function(a){function b(){f.scrollTop(0);d.removeClass("select2-active"); -k.positionDropdown()}function c(a){f.html(k.opts.escapeMarkup(a));b()}var d=this.search,f=this.results,h=this.opts,i,k=this;if(!(!0!==a&&(!1===this.showSearchInput||!this.opened()))){d.addClass("select2-active");if(1<=h.maximumSelectionSize&&(i=this.data(),e.isArray(i)&&i.length>=h.maximumSelectionSize&&u(h.formatSelectionTooBig,"formatSelectionTooBig"))){c("
            • "+h.formatSelectionTooBig(h.maximumSelectionSize)+"
            • ");return}d.val().length"+h.formatInputTooShort(d.val(),h.minimumInputLength)+""):(c("
            • "+h.formatSearching()+"
            • "),i=this.tokenize(),i!=g&&null!=i&&d.val(i),this.resultsPage=1,h.query({term:d.val(),page:this.resultsPage,context:null,matcher:h.matcher,callback:this.bind(function(i){var l;this.opened()&&((this.context=i.context===g?null:i.context,this.opts.createSearchChoice&&""!==d.val()&&(l=this.opts.createSearchChoice.call(null, -d.val(),i.results),l!==g&&null!==l&&k.id(l)!==g&&null!==k.id(l)&&0===e(i.results).filter(function(){return m(k.id(this),k.id(l))}).length&&i.results.unshift(l)),0===i.results.length&&u(h.formatNoMatches,"formatNoMatches"))?c("
            • "+h.formatNoMatches(d.val())+"
            • "):(f.empty(),k.opts.populateResults.call(this,f,i.results,{term:d.val(),page:this.resultsPage,context:null}),!0===i.more&&u(h.formatLoadMore,"formatLoadMore")&&(f.append("
            • "+k.opts.escapeMarkup(h.formatLoadMore(this.resultsPage))+ -"
            • "),window.setTimeout(function(){k.loadMoreIfNeeded()},10)),this.postprocessResults(i,a),b()))})}))}},cancel:function(){this.close()},blur:function(){this.close();this.container.removeClass("select2-container-active");this.dropdown.removeClass("select2-drop-active");this.search[0]===document.activeElement&&this.search.blur();this.clearSearch();this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){this.search.show();this.search.focus(); -window.setTimeout(this.bind(function(){this.search.show();this.search.focus();this.search.val(this.search.val())}),10)},selectHighlighted:function(){var a=this.highlight(),b=this.results.find(".select2-highlighted").not(".select2-disabled"),c=b.closest(".select2-result-selectable").data("select2-data");c&&(b.addClass("select2-disabled"),this.highlight(a),this.onSelect(c))},getPlaceholder:function(){return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")|| -this.opts.placeholder},initContainerWidth:function(){var a=function(){var a,c,d,f;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth()?"auto":this.opts.element.outerWidth()+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){a=this.opts.element.attr("style");if(a!==g){a=a.split(";");d=0;for(f=a.length;d
              ",{"class":"select2-container"}).html("
              ")}, -opening:function(){this.search.show();this.parent.opening.apply(this,arguments);this.dropdown.removeClass("select2-offscreen")},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.dropdown.removeAttr("style").addClass("select2-offscreen").insertAfter(this.selection).show())},focus:function(){this.close();this.selection.focus()},isFocused:function(){return this.selection[0]===document.activeElement},cancel:function(){this.parent.cancel.apply(this,arguments);this.selection.focus()}, -initContainer:function(){var a,b=this.dropdown;this.selection=a=this.container.find(".select2-choice");this.search.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(this.opened())switch(a.which){case f.UP:case f.DOWN:this.moveHighlight(a.which===f.UP?-1:1);l(a);break;case f.TAB:case f.ENTER:this.selectHighlighted();l(a);break;case f.ESC:this.cancel(a),l(a)}else a.which===f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC||!1=== -this.opts.openOnEnter&&a.which===f.ENTER||this.open()}));this.search.bind("focus",this.bind(function(){this.selection.attr("tabIndex","-1")}));this.search.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.selection.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("mousedown",this.bind(function(){this.opened()?(this.close(),this.selection.focus()):this.enabled&&this.open()}));b.bind("mousedown", -this.bind(function(){this.search.focus()}));a.bind("focus",this.bind(function(){this.container.addClass("select2-container-active");this.search.attr("tabIndex","-1")}));a.bind("blur",this.bind(function(){this.opened()||this.container.removeClass("select2-container-active");window.setTimeout(this.bind(function(){this.search.attr("tabIndex",this.opts.element.attr("tabIndex"))}),10)}));a.bind("keydown",this.bind(function(a){if(this.enabled)if(a.which===f.PAGE_UP||a.which===f.PAGE_DOWN)l(a);else if(!(a.which=== -f.TAB||f.isControl(a)||f.isFunctionKey(a)||a.which===f.ESC)&&!(!1===this.opts.openOnEnter&&a.which===f.ENTER))if(a.which==f.DELETE)this.opts.allowClear&&this.clear();else{this.open();if(a.which!==f.ENTER&&!(48>a.which)){var b=String.fromCharCode(a.which).toLowerCase();a.shiftKey&&(b=b.toUpperCase());this.search.focus();this.search.val(b)}l(a)}}));a.delegate("abbr","mousedown",this.bind(function(a){this.enabled&&(this.clear(),l(a),this.close(),this.triggerChange(),this.selection.focus())}));this.setPlaceholder(); -this.search.bind("focus",this.bind(function(){this.container.addClass("select2-container-active")}))},clear:function(){this.opts.element.val("");this.selection.find("span").empty();this.selection.removeData("select2-data");this.setPlaceholder()},initSelection:function(){if(""===this.opts.element.val())this.close(),this.setPlaceholder();else{var a=this;this.opts.initSelection.call(null,this.opts.element,function(b){b!==g&&null!==b&&(a.updateSelection(b),a.close(),a.setPlaceholder())})}},prepareOpts:function(){var a= -this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=a.find(":selected");e.isFunction(c)&&c({id:d.attr("value"),text:d.text()})});return a},setPlaceholder:function(){var a=this.getPlaceholder();""===this.opts.element.val()&&a!==g&&!(this.select&&""!==this.select.find("option:first").text())&&(this.selection.find("span").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.selection.find("abbr").hide())}, -postprocessResults:function(a,b){var c=0,d=this,f=!0;this.results.find(".select2-result-selectable").each2(function(a,b){if(m(d.id(b.data("select2-data")),d.opts.element.val()))return c=a,!1});this.highlight(c);!0===b&&(f=this.showSearchInput=F(a.results)>=this.opts.minimumResultsForSearch,this.dropdown.find(".select2-search")[f?"removeClass":"addClass"]("select2-search-hidden"),e(this.dropdown,this.container)[f?"addClass":"removeClass"]("select2-with-searchbox"))},onSelect:function(a){var b=this.opts.element.val(); -this.opts.element.val(this.id(a));this.updateSelection(a);this.close();this.selection.focus();m(b,this.id(a))||this.triggerChange()},updateSelection:function(a){var b=this.selection.find("span");this.selection.data("select2-data",a);b.empty();a=this.opts.formatSelection(a,b);a!==g&&b.append(this.opts.escapeMarkup(a));this.selection.removeClass("select2-default");this.opts.allowClear&&this.getPlaceholder()!==g&&this.selection.find("abbr").show()},val:function(){var a,b=null,c=this;if(0===arguments.length)return this.opts.element.val(); -a=arguments[0];if(this.select)this.select.val(a).find(":selected").each2(function(a,c){b={id:c.attr("value"),text:c.text()};return!1}),this.updateSelection(b),this.setPlaceholder();else{if(this.opts.initSelection===g)throw Error("cannot call val() if initSelection() is not defined");a?(this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){c.opts.element.val(!a?"":c.id(a));c.updateSelection(a);c.setPlaceholder()})):this.clear()}},clearSearch:function(){this.search.val("")}, -data:function(a){var b;if(0===arguments.length)return b=this.selection.data("select2-data"),b==g&&(b=null),b;!a||""===a?this.clear():(this.opts.element.val(!a?"":this.id(a)),this.updateSelection(a))}});z=x(w,{createContainer:function(){return e("
              ",{"class":"select2-container select2-container-multi"}).html("
              ")}, -prepareOpts:function(){var a=this.parent.prepareOpts.apply(this,arguments);"select"===a.element.get(0).tagName.toLowerCase()&&(a.initSelection=function(a,c){var d=[];a.find(":selected").each2(function(a,b){d.push({id:b.attr("value"),text:b.text()})});e.isFunction(c)&&c(d)});return a},initContainer:function(){var a;this.searchContainer=this.container.find(".select2-search-field");this.selection=a=this.container.find(".select2-choices");this.search.bind("keydown",this.bind(function(b){if(this.enabled){if(b.which=== -f.BACKSPACE&&""===this.search.val()){this.close();var c;c=a.find(".select2-search-choice-focus");if(0i(d.id(this),b)&&(b.push(d.id(this)),c.push(this))});a=c;this.selection.find(".select2-search-choice").remove();e(a).each(function(){d.addSelectedChoice(this)});d.postprocessResults()},tokenize:function(){var a=this.search.val(),a=this.opts.tokenizer(a,this.data(),this.bind(this.onSelect), -this.opts);null!=a&&a!=g&&(this.search.val(a),0
              "), -c=this.id(a),d=this.getVal(),f;f=this.opts.formatSelection(a,b);b.find("div").replaceWith("
              "+this.opts.escapeMarkup(f)+"
              ");b.find(".select2-search-choice-close").bind("mousedown",l).bind("click dblclick",this.bind(function(a){this.enabled&&(e(a.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(e(a.target));this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");this.close();this.focusSearch()})).dequeue(), -l(a))})).bind("focus",this.bind(function(){this.enabled&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))}));b.data("select2-data",a);b.insertBefore(this.searchContainer);d.push(c);this.setVal(d)},unselect:function(a){var b=this.getVal(),c,d,a=a.closest(".select2-search-choice");if(0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data");d=i(this.id(c),b);0<=d&&(b.splice(d,1),this.setVal(b),this.select&& -this.postprocessResults());a.remove();this.triggerChange({removed:c})},postprocessResults:function(){var a=this.getVal(),b=this.results.find(".select2-result-selectable"),c=this.results.find(".select2-result-with-children"),d=this;b.each2(function(b,c){var e=d.id(c.data("select2-data"));0<=i(e,a)?c.addClass("select2-disabled").removeClass("select2-result-selectable"):c.removeClass("select2-disabled").addClass("select2-result-selectable")});c.each2(function(a,b){0==b.find(".select2-result-selectable").length? -b.addClass("select2-disabled"):b.removeClass("select2-disabled")});b.each2(function(a,b){if(!b.hasClass("select2-disabled")&&b.hasClass("select2-result-selectable"))return d.highlight(0),!1})},resizeSearch:function(){var a,b,c,d,f=this.search.outerWidth()-this.search.width();a=this.search;q||(c=a[0].currentStyle||window.getComputedStyle(a[0],null),q=e("
              ").css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle, -fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),e("body").append(q));q.text(a.val());a=q.width()+10;b=this.search.offset().left;c=this.selection.width();d=this.selection.offset().left;b=c-(b-d)-f;bb&&(b=c-f);this.search.width(b)},getVal:function(){var a;if(this.select)return a=this.select.val(),null===a?[]:a;a=this.opts.element.val();return s(a,this.opts.separator)},setVal:function(a){var b;this.select?this.select.val(a):(b= -[],e(a).each(function(){0>i(this,b)&&b.push(this)}),this.opts.element.val(0===b.length?"":b.join(this.opts.separator)))},val:function(){var a,b=[],c=this;if(0===arguments.length)return this.getVal();if(a=arguments[0])if(this.setVal(a),this.select)this.select.find(":selected").each(function(){b.push({id:e(this).attr("value"),text:e(this).text()})}),this.updateSelection(b);else{if(this.opts.initSelection===g)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element, -function(a){var b=e(a).map(c.id);c.setVal(b);c.updateSelection(a);c.clearSearch()})}else this.opts.element.val(""),this.updateSelection([]);this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to instead.");this.search.width(0);this.searchContainer.hide()},onSortEnd:function(){var a=[],b=this;this.searchContainer.show();this.searchContainer.appendTo(this.searchContainer.parent());this.resizeSearch(); -this.selection.find(".select2-search-choice").each(function(){a.push(b.opts.id(e(this).data("select2-data")))});this.setVal(a);this.triggerChange()},data:function(a){var b=this,c;if(0===arguments.length)return this.selection.find(".select2-search-choice").map(function(){return e(this).data("select2-data")}).get();a||(a=[]);c=e.map(a,function(a){return b.opts.id(a)});this.setVal(c);this.updateSelection(a);this.clearSearch()}});e.fn.select2=function(){var a=Array.prototype.slice.call(arguments,0),b, -c,d,f,h="val destroy opened open close focus isFocused container onSortStart onSortEnd enable disable positionDropdown data".split(" ");this.each(function(){if(0===a.length||"object"===typeof a[0])b=0===a.length?{}:e.extend({},a[0]),b.element=e(this),"select"===b.element.get(0).tagName.toLowerCase()?f=b.element.attr("multiple"):(f=b.multiple||!1,"tags"in b&&(b.multiple=f=!0)),c=f?new z:new y,c.init(b);else if("string"===typeof a[0]){if(0>i(a[0],h))throw"Unknown method: "+a[0];d=g;c=e(this).data("select2"); -if(c!==g&&(d="container"===a[0]?c.container:c[a[0]].apply(c,a.slice(1)),d!==g))return!1}else throw"Invalid arguments to select2 plugin: "+a;});return d===g?this:d};e.fn.select2.defaults={width:"copy",closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c){b=[];B(a.text,c.term,b);return b.join("")},formatSelection:function(a){return a?a.text:g},formatResultCssClass:function(){return g},formatNoMatches:function(){return"No matches found"}, -formatInputTooShort:function(a,b){return"Please enter "+(b-a.length)+" more characters"},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return 0<=b.toUpperCase().indexOf(a.toUpperCase())},separator:",",tokenSeparators:[],tokenizer:H, -escapeMarkup:function(a){return a&&"string"===typeof a?a.replace(/&/g,"&"):a},blurOnChange:!1};window.Select2={query:{ajax:C,local:D,tags:E},util:{debounce:A,markMatch:B},"class":{"abstract":w,single:y,multi:z}}}})(jQuery); diff --git a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/smartstore.articlelist.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.articlelist.js similarity index 99% rename from src/Presentation/SmartStore.Web/Themes/Flex/Scripts/smartstore.articlelist.js rename to src/Presentation/SmartStore.Web/Scripts/smartstore.articlelist.js index 9c33d5d46e..45150427b4 100644 --- a/src/Presentation/SmartStore.Web/Themes/Flex/Scripts/smartstore.articlelist.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.articlelist.js @@ -15,7 +15,7 @@ if (list.parent().hasClass('artlist-carousel')) { return; } - + var drop = art.find('.art-drop'); if (drop.length > 0) { diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.column-equalizer.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.column-equalizer.js deleted file mode 100644 index 85619c4c1e..0000000000 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.column-equalizer.js +++ /dev/null @@ -1,162 +0,0 @@ -/* -* Project: SmartStore column equalizer -* Author: Murat Cakir, SmartStore AG -*/ -; -(function ($, window, document, undefined) { - - function Equalizer(cols, options) { - - var curTallest = 0, - curRowStart = 0, - curParent = null, // the current offset parent (as element, not jq) - rowCols = [], - // HashTable : string = part name, obj = all parts (jq) - colParts = {}; - - function reset() { - rowCols = []; // empty the array - colParts = {}; // empty parts - curRowStart = 0; - curParent = null, - curTallest = 0; - } - - function getOriginalHeight(el, resize) { - // if the height has changed, return the originalHeight - if (resize) { - return el.height(); - } - var h = el.data("original-height"); - return parseFloat((h === undefined) ? el.height() : h); - } - - function setHeight(el, newHeight) { - // set the height to something new, but remember the original height in case things change - var h = el.data("original-height"); - var valign = el.data("equalized-valign"); - el.data("original-height", (h === undefined) ? parseFloat(el.css("height")) : h); - el.css("min-height", newHeight); - if (valign) { - el.css({ lineHeight: newHeight + "px", verticalAlign: "middle" }); - } - } - - function equalize(resize) { - - // find the tallest column in the row, and set the heights - // of all of the columns to match it. - cols.each(function (index) { - - var deep, parts, part; - - var col = $(this); - - deep = (options.deep === undefined || options.deep === null) ? col.data("equalized-deep") : options.deep; - - var applyHeight = function () { - - if (rowCols.length < 2) - return; // useless to equalize a 1-col row - - for (curCol = 0; curCol < rowCols.length ; ++curCol) { - - if (deep) { - // set heights of all parts in the column - // (but not the column itself) - $.each(colParts, function (name, val) { - // iterate all parts - $.each(val.elements, function () { - // and set height of all part elements in each col to the max - setHeight($(this), val.tallest); - }); - }); - } - else { - // set height of the columns only - setHeight(rowCols[curCol], curTallest); - } - - } - } - - if (curRowStart != col.position().top || !col.offsetParent().is(curParent)) { - // we just came to a new row. - // Apply all the heights on the (previous) completed row - applyHeight(); - // set the variables for the new row - rowCols.length = 0; // empty the array - colParts = {}; // empty parts - curRowStart = col.position().top; - curParent = col.offsetParent()[0]; - curTallest = getOriginalHeight(col, resize); - deep = (options.deep === undefined || options.deep === null) ? col.data("equalized-deep") : options.deep; - rowCols.push(col); - - // determine deep parts (first col in row is enough) - if (deep) { - parts = col.find("[data-equalized-part]"); - parts.each(function () { - part = $(this); - var name = part.data("equalized-part"); - if (name) { // ensure it's not empty - colParts[name] = { tallest: getOriginalHeight(part, resize), elements: [part] }; - } - }); - } - } - else { - - // another col on the current row. - // Add it to the list and check if it's taller - rowCols.push(col); - - if (deep) { - // find all parts in this sibling col - $.each(colParts, function (name, val) { - part = col.find("[data-equalized-part=" + name + "]"); - if (part.length == 1) { - val.tallest = Math.max(getOriginalHeight(part, resize), val.tallest);; - val.elements.push(part); - } - }); - } - else { - curTallest = Math.max(getOriginalHeight(col, resize), curTallest); - } - - } - // do the last row - applyHeight(); - - }); - - } - - // do the work not before all images contained within - // all columns are loaded, otherwise real heights - // cannot be determined reliably. - $.preload(cols.find("img"), equalize); - - if (options.responsive) { - EventBroker.subscribe("page.resized", function (data) { - reset(); - equalize(true); - }); - } - - // for jQuery chaining - return cols; - } - - var defaults = { - // data-equalized-deep (row-wide) - deep: undefined, - responsive: undefined - }; - - $.fn.equalizeColumns = function (options) { - return new Equalizer(this, $.extend({}, defaults, options)); - } - -})(jQuery, window, document); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js index 6d065353a9..8c9fe225fe 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.common.js @@ -1,56 +1,149 @@ (function ($, window, document, undefined) { + var viewport = ResponsiveBootstrapToolkit; + + // TODO: (mc) ABS4 > delete viewport specific stuff from ~/Scripts/public.common.js, it's shared now.' + window.getPageWidth = function () { + return parseFloat($("#page").css("width")); + } + + window.getViewport = function () { + return viewport; + } window.setLocation = function (url) { window.location.href = url; } - window.openPopup = function (url, fluid) { - var modal = $('#modal-popup-shared'); + window.openPopup = function (url, large, flex) { + var opts = $.isPlainObject(url) ? url : { + /* id, backdrop */ + url: url, + large: large, + flex: flex + }; + + var id = (opts.id || "modal-popup-shared"); + var modal = $('#' + id); + var sizeClass = ""; + + if (opts.flex === undefined) opts.flex = true; + if (opts.flex) sizeClass = "modal-flex"; + if (opts.backdrop === undefined) opts.backdrop = true; + + if (opts.large && !opts.flex) + sizeClass = "modal-lg"; + else if (!opts.large && opts.flex) + sizeClass += " modal-flex-sm"; if (modal.length === 0) { - // TODO: (mc) Update to BS4 modal html later - var html = - '