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..85bd82b7ff 100644 --- a/.gitignore +++ b/.gitignore @@ -300,6 +300,7 @@ Kopie von* /src/Presentation/SmartStore.Web/App_Data/ImportProfiles/ /src/Presentation/SmartStore.Web/App_Data/Indexing/ /src/Presentation/SmartStore.Web/App_Data/Lucene/ +/src/Presentation/SmartStore.Web/App_Data/EfCache/ /src/Presentation/SmartStore.Web/App_Data/Settings.txt /src/Presentation/SmartStore.Web/App_Data/InstalledPlugins.txt /src/Presentation/SmartStore.Web/App_Data/Licenses.lic @@ -320,3 +321,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..f8e84aa9bb 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,60 @@ -## Overview +

+ + Smartstore.NET + + + +
+
+ +

+ Smartstore.NET +

+

Ready. Sell. Grow.

+

- SmartStore.NET Demoshop + GitHub release + GitHub All Releases + GitHub repo size + GitHub closed issues + GitHub commit activity + GitHub

-SmartStore.NET is a free, open source, full-featured e-commerce solution for companies of any size. It is web standards compliant and incorporates the newest Microsoft web technology stack. +

+ Try Online ∙ + Docs ∙ + Forum ∙ + Marketplace ∙ + Translations +

+
+ +Smartstore.NET is a free, open source, full-featured e-commerce solution for companies of any size. It is web standards compliant and incorporates the newest Microsoft web technology stack. -**SmartStore.NET includes all essential features to create multilingual and multi-currency stores** targeting desktop or mobile devices and enabling SEO optimized rich product catalogs with support for an unlimited number of products and categories, variants, bundles, datasheets, ESD, discounts, coupons and many more. +**Smartstore.NET includes all essential features to create multilingual and multi-currency stores** targeting desktop or mobile devices and enabling SEO optimized rich product catalogs with support for an unlimited number of products and categories, variants, bundles, datasheets, ESD, discounts, coupons and many more. -A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling, etc. makes SmartStore.NET a powerful all-in-one solution fulfilling all your needs. +A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping handling, etc. makes Smartstore.NET a powerful all-in-one solution fulfilling all your needs. -**SmartStore.NET delivers a beautiful and configurable shop front-end out-of-the-box**, built with a design approach on the highest level, including components like `Bootstrap 4`, `Sass` and others. The supplied theme _Flex_ is modern, clean and fully responsive, giving buyers the best possible shopping experience on any device. +
+

+ Smartstore.NET Demoshop +

+
-The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-) +**Smartstore.NET delivers a beautiful and configurable shop front-end out-of-the-box**, built with a design approach on the highest level, including components like `Bootstrap 4`, `Sass` and others. The supplied theme _Flex_ is modern, clean and fully responsive, giving buyers the best possible shopping experience on any device. + +The state-of-the-art architecture of Smartstore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-) * **Website:** [http://www.smartstore.com/en/net](http://www.smartstore.com/en/net) * **Forum:** [http://community.smartstore.com](http://community.smartstore.com) * **Marketplace:** [http://community.smartstore.com/marketplace](http://community.smartstore.com/marketplace) * **Translations:** [http://translate.smartstore.com/](http://translate.smartstore.com/) -* **Documentation:** [SmartStore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home) +* **Documentation:** [Smartstore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home) + +

 

+ ## Highlights @@ -35,11 +71,13 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC ### Features +* NEW: CMS Page Builder +* NEW: CMS Menu Builder * Multi-Store support * 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. @@ -55,36 +93,28 @@ The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC * CMS (Blog, Forum, custom pages & HTML content etc.) * 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 -* ...and a lot more new features, enhancements and fixes +Smartstore.NET V3.2.0 has been released on 10 May, 2019. The highlights are: -\* Commercial plugin +* (NEW) **Page Builder**: Create fascinating content that will boost your sales. No coding is required thanks to a powerful WYSIWYG editor which utilizes the revolutionary *CSS Grid* system (commercial plugin) +* (NEW) **Menu Builder**: Visual manager for all sorts of menus. Change existing menus or create your own and place them anywhere you want. +* (Perf) **Faster MegaSearch** thanks to Lucene.NET 4.8 +* (Perf) **Huge performance increase** in discount resolution and calculation. ## Try it online -We have set up a live online demo for you so you are able to test SmartStore.NET without local installation. Get a first impression and test all available features in the front- and in the backend. Please keep in mind that the backend demo is shared and other testers can modify data at the same time. +We have set up a live online demo for you so you are able to test Smartstore.NET without local installation. Get a first impression and test all available features in the front- and in the backend. Please keep in mind that the backend demo is shared and other testers can modify data at the same time. * [**Frontend**](http://frontend.smartstore.net/en) (User: demo, PWD: 1234) * [**Backend**](http://backend.smartstore.net/en/login) (User: demo, PWD: 1234) + + ## How to install * Download the latest stable release from the download tab and unzip it to your web folder @@ -92,7 +122,7 @@ We have set up a live online demo for you so you are able to test SmartStore.NET * Fire up your browser and follow the installation instructions * Enjoy ;-) -NOTE: SmartStore.NET 3 requires [Visual C++ Redistributable für Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=52685) which is already pre-installed on most systems. If, nevertheless, it is missing on your web server, just download and execute the installer or ask your hosting provider to do that for you. +NOTE: Smartstore.NET 3 requires [Visual C++ Redistributable für Visual Studio 2015](https://www.microsoft.com/en-US/download/details.aspx?id=52685) which is already pre-installed on most systems. If, nevertheless, it is missing on your web server, just download and execute the installer or ask your hosting provider to do that for you. ### System requirements @@ -103,6 +133,7 @@ NOTE: SmartStore.NET 3 requires [Visual C++ Redistributable für Visual Studio 2 * Full Trust + ## 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..080c78d497 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -39,7 +39,7 @@ x86 $(BUILD_NUMBER) - 3.0.3 + 3.2.2 $(StageFolder) .$(Version) @@ -90,21 +90,20 @@ - - - - - - - - - - + + + + + + + + + @@ -126,6 +125,7 @@ + @@ -137,9 +137,9 @@ OutDir=$(StageFolder)\Administration\bin\;Configuration=$(Configuration)" /> - - - + + + @@ -159,11 +159,11 @@ - - + + - - + + diff --git a/assets/SmartStoreNET.png b/assets/SmartStoreNET.png deleted file mode 100644 index 1fc5ca5409..0000000000 Binary files a/assets/SmartStoreNET.png and /dev/null differ diff --git a/assets/smartstore-icon.png b/assets/smartstore-icon.png new file mode 100644 index 0000000000..e6414db195 Binary files /dev/null and b/assets/smartstore-icon.png differ diff --git a/assets/smartstore-text.png b/assets/smartstore-text.png new file mode 100644 index 0000000000..25bf9558fb Binary files /dev/null and b/assets/smartstore-text.png differ diff --git a/build.bat b/build.bat index 8d1b2b2f19..94a6c72519 100644 --- a/build.bat +++ b/build.bat @@ -1,37 +1,45 @@ +SETLOCAL ENABLEDELAYEDEXPANSION + for /f "usebackq tokens=1* delims=: " %%i in (`lib\vswhere\vswhere -latest -requires Microsoft.Component.MSBuild`) do ( - if /i "%%i"=="installationPath" set InstallDir=%%j + if /i "%%i"=="installationPath" ( + set InstallDir=%%j + echo !InstallDir! + if exist "!InstallDir!\MSBuild\15.0\Bin\MSBuild.exe" ( + echo "Using MSBuild from Visual Studio 2017" + set msbuild="!InstallDir!\MSBuild\15.0\Bin\MSBuild.exe" + goto build + ) + if exist "!InstallDir!\MSBuild\Current\Bin\MSBuild.exe" ( + echo "Using MSBuild from Visual Studio 2019" + set msbuild="!InstallDir!\MSBuild\Current\Bin\MSBuild.exe" + goto build + ) + ) ) FOR %%b in ( - "%InstallDir%\Common7\Tools\VsMSBuildCmd.bat" - "%VS140COMNTOOLS%\Common7\Tools\vsvars32.bat" + "%VS150COMNTOOLS%\vsvars32.bat" + "%VS140COMNTOOLS%\vsvars32.bat" + "%VS120COMNTOOLS%\vsvars32.bat" + "%VS110COMNTOOLS%\vsvars32.bat" ) do ( if exist %%b ( - call %%b - goto findmsbuild + echo "Using MSBuild from %%b" + call %%b + set msbuild="msbuild" + goto build ) ) echo "Unable to detect suitable environment. Build may not succeed." -:findmsbuild - -SETLOCAL ENABLEDELAYEDEXPANSION - -if exist "%InstallDir%\MSBuild\15.0\Bin\MSBuild.exe" ( - if not defined MsBuildPath ( - SET "MsBuildPath=%InstallDir%\MSBuild\15.0\Bin\MsBuild.exe" - goto build - ) -) - - -echo "Unable to detect suitable MsBuild version (15.0). Build may not succeed." - :build + cd /d %~dp0 echo "Restoring NuGet packages" -lib\nuget\nuget.exe restore "src\SmartStoreNET.Full-sym.sln" +lib\nuget\nuget.exe restore "src\SmartStoreNET.sln" + +%msbuild% SmartStoreNET.proj /p:SlnName=SmartStoreNET /m /p:DebugSymbols=false /p:DebugType=None /maxcpucount %* -call "!MsBuildPath!" SmartStoreNET.proj /p:SlnName=SmartStoreNET /m /p:DebugSymbols=false /p:DebugType=None /maxcpucount %* +:end diff --git a/changelog.md b/changelog.md index 4a275eeea8..81357cca18 100644 --- a/changelog.md +++ b/changelog.md @@ -1,32 +1,318 @@ -# Release Notes +# Release Notes + +## SmartStore.NET 3.2.2 + +### New Features +* External category links: alternative links for categories in the main menu and in category listings. +* Page Builder: multistore support for stories. +* #1639 Shipping.ByTotal: Add option to calculate the shipping rate based on the net total instead of the gross total. +* #1537 Implement "Ratenzahlung powered by PayPal" provider. +* New Heidelpay payment plugin. +* Shop-Connector: mapping of product SKUs that differ between client shops. +* MegaMenu supports configuration of menu items + + +### Improvements +* Amazon Pay: PSD2. Strong customer authentication (SCA) upgrade. +* Updated *MaxMind.GeoIP* library and country database +* Removed HTML encoding for meta tags and document title. +* #1662 Quantity Units: added field for name pluralization. +* (SEO) combined SSL and canonical host 301 redirections to prevent successive redirects. +* (SEO) Activated GZIP compression for all dynamic and static responses. +* Set max. file upload size to 2 GB (globally) +* (Dev) Added a sample block for the *Page Builder* in DevTools plugin. +* Various other minor improvements. +* PayPal PLUS: paying by instalments can be selected in payment wall. +* Payone: send more information to API (e.g. shipping address) to stay as frictionless as possible for PSD2. +* PageBuilder: Added keyboard shortcuts +* #1582 Added structured data to reviews +* BMEcat: added support for import of specification attributes + +### Bugfixes +* Fixed wrong XML sitemap index numbering. +* #1646 Setting ManufacturerItemsToDisplayOnHomepage to 1 will not show any manufacturer. +* #1647 All manufacturers page: link of last character missing in alphabetical letter list. +* #1664 RTL: CMS menu issues in backend. +* #1677 Page Builder: moving and resizing blocks via drag & drop didn't work correctly in RTL. +* Do not adjust inventory twice when an order is cancelled and deleted. +* #1500 Fallback to fixed VAT rate instead of rate by region for countries that are not subject to vat. +* Fixed "The entity wrapper stored in the proxy does not reference the same proxy" when copying an export profile. +* Various other minor fixes. +* BMEcat: set TaxCategoryId & MainPictureId for newly imported products + + + +## SmartStore.NET 3.2.1 + +### Improvements +* Implemented file range support for CachedFileResult +* Sitemap should only include products visible for current customer, which is the search engine system customer +* Fixed uniform spelling for query string parameter "returnUrl" +* Fixed search engine issue (itemtype, "offers" required) on product detail page +* Summernote: solved html formatting issues thanks to "Beautifier" library +* Media middleware: simplified ETag generation & handling + +### Bugfixes + +* Mega Menu: Product rotator wasn't working +* Tiny Image: Fixed periodic IOException +* URL Rewriter: performance optimization +* Fixed more menu for main catalog navigation +* Fixed DateTime nor expressed as UTC in AzureFile & LocalFile +* Content Slider: Fixed slide display bug on surface and other touch devices +* Content Slider: Randomize slides > Value was stored in database but toggle button has always shown "off" +* Page Builder: Fixed slider bug for list blocks (brand, category, products) in Firefox +* Page Builder: fixes blurry background images in IOS +* Output Cache: action widgets didn't render when they were part of a widgetzone within a donut child action +* Multishop resolution failed under certain circumstances +* Fixed menu issue "collection was modified, enumeration operation may not execute" +* Fixed "The parameters dictionary contains a null entry for parameter 'catId' of non-nullable type 'System.Int32'" +* Fixed discount coupon sometimes wasn't applied +* Fixed "Value cannot be null. Parameter name: key" in order list +* Fixed topic link resolving by system name in multistore environment +* Async event consumers were not guaranteed to be awaited + + +## SmartStore.NET 3.2 + +### Highlights +* (NEW) **CMS Page Builder**: Create fascinating content that will boost your sales. No coding is required thanks to a powerful WYSIWYG editor which utilizes the revolutionary *CSS Grid* system (commercial plugin) +* (NEW) **CMS Menu Builder**: Visual manager for all sorts of menus. Change existing menus or create your own and place them anywhere you want. +* (Perf) **Faster MegaSearch** thanks to Lucene.NET 4.8 +* (Perf) **Huge performance increase** in discount resolution and calculation. + +### New Features +* **EmailReminder**: + * Reminders for open shopping carts + * Reminders for product reviews + * Reminders to visit the shop after a long absence +* **Scheduling** + * Task execution history + * Web farms: tasks can run decidedly on each machine of a web farm + * #1144 MegaSearch: multi server search index +* **Topics & Pages** + * Access control lists + * Grid paging & filtering + * New properties: added **IsPublished**, **Short Title** (link text) and **Intro** (teaser) properties. + * #1542: added options to set body CSS class and Html Id. +* **MegaSearch**: + * Supports searching for forum posts. + * #1172 Option to display related search terms on search page. + * Command to continue a previously aborted search index rebuild. +* **Import/export of**: + * Product tags + * Tier prices + * Product attribute options + * Product attribute combinations (update only) +* **Forum**: + * Added option to display a captcha on forum pages when creating or replying to a topic. + * #417 Restrict forum groups to specific customer roles. + * Added published property to forum topic and post. + * Added voting for forum posts. + * Several performance improvements. +* **BeezUp**: + * #1459 Add option to only submit one category name per product + * Allow to specify export categories per product +* **Santander**: + * Added payment method for instalment purchase. + * Added financing calculator. +* Updated icon library to **Font Awesome 5 Free** with the possibility to activate pro version in the frontend. +* New storefront catalog options: **ShowSubCategoriesInSubPages**, **ShowDescriptionInSubPages** & **IncludeFeaturedProductsInSubPages** (Subpage = List index > 1 or any active filter). +* New security option: Use **invisible reCAPTCHA** +* 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) +* Customer avatar: Letter with colored background if no avatar image was uploaded. +* Viveum: Supports payment via "Virtual Account Brands" (e.g. PayPal). +* Added options for alternating price display (in badges). +* #1515 Poll: Add result tab with a list of answers and customers for a poll +* BMEcat: Added export and import of product tags. +* Web-API: Added endpoints for ProductSpecificationAttribute. +* Trusted Shops: Added new Trustbadge display for mobile devices +* (Dev) Added *sm:EfCommandTimeout* setting to web.config (was 30 sec. fixed, now configurable) + +### Improvements +* (Perf) Significantly increased query performance for products with a lot of category assignments (> 10). +* (Perf) Increased app startup speed by up to 20%. +* (Perf) Ultra-fast file-based XML sitemap generation for extremely large catalogs (> 1M) + * At least 10x faster + * Generated files are saved on the hard disk now: a rebuild after an app restart is no longer necessary. + * No exclusive locks during rebuilds anymore: if an (outdated) file already exists, it is returned instantly. +* (Dev) Enhanced pub/sub: new `IConsumer` marker interface for classes that contain one or more event message handlers. The generic `IConsumer` implements `IConsumer` but has been marked as `Obsolete`. +* Better protection against XSS attacks +* Updated to Bootstrap version 4.1.3 +* **Debitoor**: + * Partially update customer instead of full update to avoid all fields being overwritten. + * #1540 Place company name in front of customer name (according to address format of the particular country). + * #1565 Add a button to manually create a draft invoice. +* #1479 Show in messages the delivery time at the time of purchase +* #1184 Sort current shopping carts & current wishlists by `ShoppingCartItem.CreatedOn`. +* #1106 BMECat: import & export support for product keywords +* #1499 Added hint to forms indicating that fields with an asterisk (*) are required +* Added filter for newsletter subscriber export by working language +* Refactored download section +* Enhanced EntityPicker to pick from customers, manufacturers & categories +* #1510 Breadcrumb of an associated product should include the grouped product if it has no assigned categories. +* OpenTrans: added customer number to parties +* Do not filter cookie using resources if cookie usage has not yet been consented to. +* #1563 QueuedMessagesClearTask: add a setting for the age of the mails to be deleted. +* #1569 Added a setting to show login note if no prices are displayed due to customer group permissions. +* PayPal PLUS: Up to 10 more third party payment methods are allowed by PayPal now. +* #1560 Tell-a-Friend and anonymous user: render alert box and tell guest to login to use this function +* #1571 Compare products now shows all specification attribute options +* #1539 Signing in is now allowed with e-mail and username +* Trusted Shops: Trustbadge won't be displayed in Popups & Iframes anymore +* #1461 Admin Grid: filter dialog will be displayed entirely even when grid has no data to display +* Lots of minor enhancements for both frontend and admin theme +* Many other minor improvements + +### Bugfixes +* In a multi-store environment, multiple topics with the same system name can now 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 + * Custom labels are now being exported +* Media middleware: 0-byte files should be treated as missing. +* Megamenu alpha/omega blends do now 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: When inserting a record the country now will be saved +* #1460 Editing of the customer title was 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 +* Fixed Autofac circularity error +* When creating a topic, the widget zone input has shown 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 was wrong if attribute combinations with text types were involved. +* #1485 Dropdown list for product sorting didn't not work with Internet Explorer 11. +* #1468 Twitter authentication wasn't working anymore. +* Newsletter subscription didn't work when customer privacy setting DisplayGdprConsentOnForms was turned off +* Fixed social media image detection +* Fixed redirection of bots when several languages were active +* Region cannot be selected in checkout when entering a billing or shipping address +* Fixed invalid conversion of "System.Int32" to "SmartStore.Core.Domain.Tax.VatNumberStatus" when placing an order +* MegaMenu: Improved item rendering for third tier elements +* Product display order on category and manufacturer pages was sometimes wrong when using linq search. +* Debitoor: Wrong invoice total if a subtotal discount has been applied. +* Import: Fixes invalid conversion "System.Double" to "SmartStore.Core.Domain.Catalog.QuantityControlType". +* Topics: Fixes "Cannot insert duplicate key row in object 'dbo.UrlRecord' with unique index 'IX_UrlRecord_Slug'". +* #1566 Santander: eliminate the 1 cent rounding difference at amountTotalNet. +* Fixed redirection to the homepage for pages which are loaded while the application is restarted. +* #1570 Filter option "Only deactivated customers" filters deleted instead of deactivated customers. +* #1475 select boxes must be wrapped on mobile devices if data-select-url is set +* Fixed the redirection to the homepage for pages which were loaded while the application was restarted +* Fixes product feeds expect a different base price formatting. +* #1369 Shopping cart shows "Discount code applied", although it is not applied due to a lower tier price. +* Many other fixes... + + +## 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 @@ -38,12 +324,17 @@ * 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 @@ -55,6 +346,10 @@ * #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 @@ -74,7 +369,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 @@ -141,7 +438,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 @@ -463,7 +762,8 @@ * #495 Implement option to search product detail description by default ### Improvements -* (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. +* (Perf) Implemented static caches for URL aliases and localized properties. Increases +* and request speed by up to 30%. * (Perf) Significantly reduced number of database reads during product list rendering. Increases request speed by up to 10%. * (Perf) Implemented 2nd level cache for infrequently changed entities. Increases request speed by up to 10%. * TaskScheduler: Rewritten from scratch to be suitable for Web Farms @@ -1083,7 +1383,7 @@ * [SMNET-202] - SmartTabSelection mit verschachtelten Tabs fehlerhaft nach Reload einer Seite ### Improvement - + * [SMNET-13] - Attributwerte: der Text "Aufpreis" muss um "Minderpreis" erweitert werden. * [SMNET-15] - Umgestaltung der Darstellung der Staffelpreise (Popover ab dem fünften Element) * [SMNET-30] - MessageTemplates teilweise auf Englisch diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll index 2c82c7853f..17bf4e2ad9 100644 Binary files a/lib/SmartStore.Licensing/SmartStore.Licensing.dll and b/lib/SmartStore.Licensing/SmartStore.Licensing.dll differ diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs index 79225de4c7..b58492422f 100644 --- a/src/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfo.cs @@ -7,6 +7,6 @@ [assembly: AssemblyDescription("SmartStore.NET")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("SmartStore AG")] -[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")] +[assembly: AssemblyCopyright("Copyright © SmartStore AG 2019")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 63fae0f3e7..719cd1d6ff 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.2.2.0")] -[assembly: AssemblyFileVersion("3.0.3.0")] -[assembly: AssemblyInformationalVersion("3.0.3.0")] +[assembly: AssemblyFileVersion("3.2.2.0")] +[assembly: AssemblyInformationalVersion("3.2.2.0")] diff --git a/src/Libraries/SmartStore.Core/Async/AsyncLock.cs b/src/Libraries/SmartStore.Core/Async/AsyncLock.cs deleted file mode 100644 index b93da92afc..0000000000 --- a/src/Libraries/SmartStore.Core/Async/AsyncLock.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using SmartStore.Utilities; - -namespace SmartStore.Core.Async -{ - public sealed class AsyncLock : DisposableObject - { - private static readonly TimeSpan _noTimeout = TimeSpan.FromMilliseconds(-1); - private static readonly ConcurrentDictionary _keyLocks = new ConcurrentDictionary(); - - private readonly SemaphoreSlim _semaphore; - private readonly int _initialCount; - - public AsyncLock() - : this(1, int.MaxValue) - { - } - - public AsyncLock(int initialCount) - : this(initialCount, int.MaxValue) - { - } - - public AsyncLock(int initialCount, int maxCount) - { - _semaphore = new SemaphoreSlim(initialCount, maxCount); - _initialCount = initialCount; - } - - public static AsyncLock Acquire(object key) - { - Guard.NotNull(key, nameof(key)); - - var asyncLock = _keyLocks.GetOrAdd(key, x => new AsyncLock()); - - // Perf: once we have more than 100 lock objects in the storage, remove the idle ones. - if (_keyLocks.Count > 100) - { - // never remove the current one and the locked ones - var idles = _keyLocks.Where(kvp => kvp.Key != key && !kvp.Value.IsLockHeld).ToArray(); - foreach (var kvp in idles) - { - var value = kvp.Value; - // double check for held lock - if (!value.IsLockHeld) - { - _keyLocks.TryRemove(kvp.Key, out value); - } - } - } - - return asyncLock; - } - - public bool IsLockHeld - { - get { return _semaphore.CurrentCount < _initialCount; } - } - - public int Release() - { - return _semaphore.Release(); - } - - public int Release(int releaseCount) - { - return _semaphore.Release(releaseCount); - } - - public Task LockAsync() - { - return LockAsync(_noTimeout, CancellationToken.None); - } - - public Task LockAsync(TimeSpan timeout) - { - return LockAsync(timeout, CancellationToken.None); - } - - public Task LockAsync(CancellationToken cancellationToken) - { - return LockAsync(_noTimeout, cancellationToken); - } - - public Task LockAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - var wait = _semaphore.WaitAsync(timeout, cancellationToken); - - return wait.IsCompleted - ? Task.FromResult(new ActionDisposable(() => _semaphore.Release()) as IDisposable) - : wait.ContinueWith((t, s) => new ActionDisposable(() => ((SemaphoreSlim)s).Release()) as IDisposable, - _semaphore, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default); - } - - protected override void OnDispose(bool disposing) - { - if (disposing) - _semaphore.Dispose(); - } - } -} diff --git a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs index 1fce2cc436..2751259eef 100644 --- a/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs +++ b/src/Libraries/SmartStore.Core/Async/AsyncRunner.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -8,113 +9,166 @@ namespace SmartStore.Core.Async { - public static class AsyncRunner + using EventQueue = ConcurrentQueue>; + + public class AsyncRunner : IDisposable { - private static readonly BackgroundWorkHost _host = new BackgroundWorkHost(); + #region Instance members - /// - /// Gets the global cancellation token which signals the application shutdown - /// - public static CancellationToken AppShutdownCancellationToken + private ExclusiveSynchronizationContext CurrentContext; + private SynchronizationContext OldContext; + private int TaskCount; + + private AsyncRunner() { - get { return _host.ShutdownCancellationTokenSource.Token; } + OldContext = SynchronizationContext.Current; + CurrentContext = new ExclusiveSynchronizationContext(OldContext); + SynchronizationContext.SetSynchronizationContext(CurrentContext); + } + + public void Dispose() + { + try + { + CurrentContext.BeginMessageLoop(); + } + catch (Exception e) + { + throw e; + } + finally + { + SynchronizationContext.SetSynchronizationContext(OldContext); + } + } + + private void Increment() + { + Interlocked.Increment(ref TaskCount); + } + + private void Decrement() + { + Interlocked.Decrement(ref TaskCount); + if (TaskCount == 0) + { + CurrentContext.EndMessageLoop(); + } } /// /// Executes an async Task method which has a void return value synchronously /// - /// Task method to execute - public static void RunSync(Func func) + /// Task execute + public void Run(Task task, Action continuation = null) { - var oldContext = SynchronizationContext.Current; - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - synch.Post(async _ => + CurrentContext.Post(async _ => { try { - await func(); + Increment(); + await task; + + continuation?.Invoke(task); } catch (Exception e) { - synch.InnerException = e; - throw; + CurrentContext.InnerException = e; } finally { - synch.EndMessageLoop(); + Decrement(); } }, null); - synch.BeginMessageLoop(); - - SynchronizationContext.SetSynchronizationContext(oldContext); } /// /// Executes an async Task method which has a TResult return type synchronously /// /// Return Type - /// Task method to execute - /// - public static TResult RunSync(Func> func) + /// Task to execute + public TResult Run(Task task, Action> continuation = null) { - var oldContext = SynchronizationContext.Current; - var synch = new ExclusiveSynchronizationContext(); - SynchronizationContext.SetSynchronizationContext(synch); - TResult ret = default(TResult); - synch.Post(async _ => + var result = default(TResult); + + CurrentContext.Post(async _ => { try { - ret = await func(); + Increment(); + result = await task; + + continuation?.Invoke(task); } catch (Exception e) { - synch.InnerException = e; - throw; + CurrentContext.InnerException = e; } finally { - synch.EndMessageLoop(); + Decrement(); } }, null); - synch.BeginMessageLoop(); - SynchronizationContext.SetSynchronizationContext(oldContext); - return ret; - } - public static Task Run(Action action) - { - return Run(action, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); + return result; } - public static Task Run(Action action, CancellationToken cancellationToken) + #endregion + + private static readonly BackgroundWorkHost _host = new BackgroundWorkHost(); + + /// + /// Gets the global cancellation token which signals the application shutdown + /// + public static CancellationToken AppShutdownCancellationToken { - return Run(action, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); + get { return _host.ShutdownCancellationTokenSource.Token; } } - public static Task Run(Action action, TaskCreationOptions options) + public static AsyncRunner Create() { - return Run(action, CancellationToken.None, options, TaskScheduler.Default); + return new AsyncRunner(); } - public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options) + /// + /// Executes an async Task method which has a void return value synchronously + /// + /// Task method to execute + public static void RunSync(Func func, Action continuation = null) { - return Run(action, cancellationToken, options, TaskScheduler.Default); + using (var runner = new AsyncRunner()) + { + runner.Run(func(), continuation); + } } - public static Task Run(Action action, TaskScheduler scheduler) + /// + /// Executes an async Task method which has a TResult return type synchronously + /// + /// Return Type + /// Task method to execute + /// + public static TResult RunSync(Func> func, Action> continuation = null) { - return Run(action, CancellationToken.None, TaskCreationOptions.None, scheduler); + var result = default(TResult); + + using (var runner = new AsyncRunner()) + { + result = runner.Run(func(), continuation); + } + + return result; } - public static Task Run(Action action, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + public static Task Run( + Action action, + CancellationToken cancellationToken = default(CancellationToken), + TaskCreationOptions options = TaskCreationOptions.LongRunning, + TaskScheduler scheduler = null) { Guard.NotNull(action, nameof(action)); - Guard.NotNull(scheduler, nameof(scheduler)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew(() => { var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; @@ -122,21 +176,24 @@ public static Task Run(Action action, Cancell { action(accessor.GetLifetimeScope(null), ct); } - }, ct, options, scheduler); + }, ct, options, scheduler ?? TaskScheduler.Default); _host.Register(t, ct); return t; } - public static Task Run(Action action, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + public static Task Run( + Action action, + object state, + CancellationToken cancellationToken = default(CancellationToken), + TaskCreationOptions options = TaskCreationOptions.LongRunning, + TaskScheduler scheduler = null) { Guard.NotNull(state, nameof(state)); Guard.NotNull(action, nameof(action)); - Guard.NotNull(scheduler, nameof(scheduler)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew((o) => { @@ -145,47 +202,22 @@ public static Task Run(Action action, { action(accessor.GetLifetimeScope(null), ct, o); } - }, state, ct, options, scheduler); + }, state, ct, options, scheduler ?? TaskScheduler.Default); _host.Register(t, ct); return t; } - - - public static Task Run(Func function) - { - return Run(function, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); - } - - public static Task Run(Func function, CancellationToken cancellationToken) - { - return Run(function, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default); - } - - public static Task Run(Func function, TaskCreationOptions options) - { - return Run(function, CancellationToken.None, options, TaskScheduler.Default); - } - - public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options) - { - return Run(function, cancellationToken, options, TaskScheduler.Default); - } - - public static Task Run(Func function, TaskScheduler scheduler) - { - return Run(function, CancellationToken.None, TaskCreationOptions.None, scheduler); - } - - public static Task Run(Func function, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + public static Task Run( + Func function, + CancellationToken cancellationToken = default(CancellationToken), + TaskCreationOptions options = TaskCreationOptions.LongRunning, + TaskScheduler scheduler = null) { Guard.NotNull(function, nameof(function)); - Guard.NotNull(scheduler, nameof(scheduler)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew(() => { @@ -194,21 +226,24 @@ public static Task Run(Func Run(Func function, object state, CancellationToken cancellationToken, TaskCreationOptions options, TaskScheduler scheduler) + public static Task Run( + Func function, + object state, + CancellationToken cancellationToken = default(CancellationToken), + TaskCreationOptions options = TaskCreationOptions.LongRunning, + TaskScheduler scheduler = null) { Guard.NotNull(state, nameof(state)); Guard.NotNull(function, nameof(function)); - Guard.NotNull(scheduler, nameof(scheduler)); var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; - options |= TaskCreationOptions.LongRunning; // enforce an exclusive thread (not from pool) var t = Task.Factory.StartNew((o) => { @@ -217,19 +252,61 @@ public static Task Run(Func function, + object state, + CancellationToken cancellationToken = default(CancellationToken)) + { + Guard.NotNull(state, nameof(state)); + Guard.NotNull(function, nameof(function)); + + var ct = _host.CreateCompositeCancellationTokenSource(cancellationToken).Token; + + var accessor = EngineContext.Current.ContainerManager.ScopeAccessor; + var scope = accessor.BeginLifetimeScope(null); + + Task task = null; + + try + { + task = function(scope, ct).ContinueWith(x => + { + scope.Dispose(); + }); + _host.Register(task, ct); + } + catch + { + scope.Dispose(); + } + + return task; + } private class ExclusiveSynchronizationContext : SynchronizationContext { private bool _done; - readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); - readonly Queue> _items = new Queue>(); + private readonly AutoResetEvent _workItemsWaiting = new AutoResetEvent(false); + private readonly EventQueue _items; + + public ExclusiveSynchronizationContext(SynchronizationContext old) + { + if (old is ExclusiveSynchronizationContext oldEx) + { + this._items = oldEx._items; + } + else + { + this._items = new EventQueue(); + } + } public Exception InnerException { get; set; } @@ -240,10 +317,7 @@ public override void Send(SendOrPostCallback d, object state) public override void Post(SendOrPostCallback d, object state) { - lock (_items) - { - _items.Enqueue(Tuple.Create(d, state)); - } + _items.Enqueue(Tuple.Create(d, state)); _workItemsWaiting.Set(); } @@ -256,26 +330,19 @@ public void BeginMessageLoop() { while (!_done) { - Tuple task = null; - lock (_items) + if (!_items.TryDequeue(out var task)) { - if (_items.Count > 0) - { - task = _items.Dequeue(); - } + _workItemsWaiting.WaitOne(); } - if (task != null) + else { task.Item1(task.Item2); - if (InnerException != null) // the method threw an exeption + + if (InnerException != null) // method threw an exeption { throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); } } - else - { - _workItemsWaiting.WaitOne(); - } } } @@ -291,7 +358,7 @@ internal class BackgroundWorkHost : IRegisteredObject { private readonly CancellationTokenSource _shutdownCancellationTokenSource = new CancellationTokenSource(); private int _numRunningWorkItems; - + public BackgroundWorkHost() { HostingEnvironment.RegisterObject(this); @@ -337,7 +404,7 @@ public void Register(Task work, CancellationToken cancellationToken) } _numRunningWorkItems++; } - + work.ContinueWith( WorkItemComplete, CancellationToken.None, @@ -353,7 +420,7 @@ private void WorkItemComplete(Task work) lock (this) { num = --_numRunningWorkItems; - isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested; + isCancellationRequested = _shutdownCancellationTokenSource.IsCancellationRequested; } if (num == 0 && isCancellationRequested) { diff --git a/src/Libraries/SmartStore.Core/BaseEntity.cs b/src/Libraries/SmartStore.Core/BaseEntity.cs index c649970d6d..3fd0016909 100644 --- a/src/Libraries/SmartStore.Core/BaseEntity.cs +++ b/src/Libraries/SmartStore.Core/BaseEntity.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using System.Data.Entity.Core.Objects; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; namespace SmartStore.Core -{ - +{ /// /// Base class for entities /// @@ -21,18 +21,27 @@ public abstract partial class BaseEntity : IEquatable [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public virtual string GetEntityName() + { + return GetUnproxiedType().Name; + } + public Type GetUnproxiedType() { - var t = GetType(); - if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity.")) - { - // it's a proxied type - t = t.BaseType; - } + #region Old + //var t = GetType(); + //if (t.AssemblyQualifiedName.StartsWith("System.Data.Entity.")) + //{ + // // it's a proxied type + // t = t.BaseType; + //} - return t; - } + //return t; + #endregion + + return ObjectContext.GetObjectType(GetType()); + } /// /// Transient objects are not associated with an item already in storage. For instance, diff --git a/src/Libraries/SmartStore.Core/Caching/CacheScope.cs b/src/Libraries/SmartStore.Core/Caching/CacheScope.cs new file mode 100644 index 0000000000..b705ab10b6 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/CacheScope.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Core.Caching +{ + public class CacheScope + { + private HashSet _dependencies; + + public CacheScope(string key) + { + Key = key; + } + + public string Key { get; private set; } + + public void AddDependency(string key) + { + if (key == null) + { + return; + } + + if (_dependencies == null) + { + _dependencies = new HashSet(); + } + + _dependencies.Add(key); + } + + public IEnumerable Dependencies + { + get + { + return _dependencies ?? Enumerable.Empty(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs b/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs new file mode 100644 index 0000000000..b04e0a7d45 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/CacheScopeAccessor.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Utilities; + +namespace SmartStore.Core.Caching +{ + public class CacheScopeAccessor : ICacheScopeAccessor + { + private readonly Stack _stack = new Stack(); + + public CacheScopeAccessor() + { + } + + public CacheScope Current + { + get + { + _stack.TryPeek(out var result); + return result; + } + } + + public bool HasScope(string key) + { + return _stack.Any(x => x.Key.IsCaseInsensitiveEqual(key)); + } + + public void PropagateKey(string key) + { + var current = Current; + if (current != null) + { + current.AddDependency(key); + } + } + + public IDisposable BeginScope(string key, bool independent = false) + { + if (Current?.Key == key) + { + // Void if parent scope has the same key. + return ActionDisposable.Empty; + } + + _stack.Push(new CacheScope(key)); + + var action = new ActionDisposable(() => + { + _stack.TryPop(out _); + + if (!independent) + { + PropagateKey(key); + } + }); + + return action; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs index c28fc3d0ff..855349106f 100644 --- a/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/ICacheManager.cs @@ -14,8 +14,9 @@ public interface ICacheManager /// /// The type of the item to get /// The cache item key + /// When true, no attemp will be made to invalidate depending/parent cache entries. /// Cached item value or null if item with specified key does not exist in the cache - T Get(string key); + T Get(string key, bool independent = false); /// /// Gets a cache item associated with the specified key or adds the item @@ -25,8 +26,9 @@ public interface ICacheManager /// The cache item key /// Func which returns value to be added to the cache /// Absolute expiration time + /// When true, no attemp will be made to invalidate depending/parent cache entries. /// Cached item value - T Get(string key, Func acquirer, TimeSpan? duration = null); + T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false); /// /// Gets a cache item associated with the specified key or - if it doesn't exist in the cache - @@ -36,16 +38,18 @@ public interface ICacheManager /// The cache item key /// Func which returns value to be added to the cache /// Absolute expiration time + /// When true, no attemp will be made to invalidate depending/parent cache entries. /// Cached item value - Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null); + Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false); /// /// Gets a hashset associated with the specified key or. If key does not exist, /// a new set is created and put to cache automatically /// /// The set cache item key + /// Optional acquirer callback that is invoked when requested set does not exist yet. /// Cached item value - ISet GetHashSet(string key); + ISet GetHashSet(string key, Func> acquirer = null); /// /// Adds a cache item with the specified key @@ -53,7 +57,10 @@ public interface ICacheManager /// Key /// Value /// Absolute expiration time - void Put(string key, object value, TimeSpan? duration = null); + /// + /// A list of keys for cache entries that should be removed from the cache whenever this entry changes. + /// + void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null); /// /// Gets a value indicating whether the value associated with the specified key is cached diff --git a/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs b/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs new file mode 100644 index 0000000000..a640e6b200 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Caching/ICacheScopeAccessor.cs @@ -0,0 +1,34 @@ +using System; + +namespace SmartStore.Core.Caching +{ + public interface ICacheScopeAccessor + { + /// + /// Returns the current cache acquire scope (for current thread) + /// + CacheScope Current { get; } + + /// + /// Whether any scope with the passed exists already. Used to prevent lock recursions. + /// + /// Key + bool HasScope(string key); + + /// + /// Notifies the current scope about a cache dependency. + /// + /// + void PropagateKey(string key); + + /// + /// Begins a new cache acquire scope. + /// + /// Key + /// + /// true means that no key propagation will be performed, making the current cache entry independent from its parent. + /// Meaning: parent cache entries will not be auto-invalidated when this entry changes. + /// + IDisposable BeginScope(string key, bool independent = false); + } +} diff --git a/src/Libraries/SmartStore.Core/Caching/ISet.cs b/src/Libraries/SmartStore.Core/Caching/ISet.cs index 59decb5d52..d0bff72bd7 100644 --- a/src/Libraries/SmartStore.Core/Caching/ISet.cs +++ b/src/Libraries/SmartStore.Core/Caching/ISet.cs @@ -6,6 +6,7 @@ namespace SmartStore.Core.Caching public interface ISet : IEnumerable { bool Add(string item); + void AddRange(IEnumerable items); void Clear(); bool Contains(string item); bool Remove(string item); diff --git a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs index 89a2c707e6..a00224784d 100644 --- a/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/MemoryCacheManager.cs @@ -4,27 +4,37 @@ using System.Threading.Tasks; using System.Collections.Concurrent; using System.Threading; -using SmartStore.Core.Async; -using System.Collections; using System.Collections.Generic; using SmartStore.Utilities; using System.Text.RegularExpressions; +using System.Diagnostics; +using SmartStore.Utilities.Threading; +using SmartStore.Core.Infrastructure.DependencyManagement; namespace SmartStore.Core.Caching { public partial class MemoryCacheManager : DisposableObject, ICacheManager { + const string LockRecursionExceptionMessage = "Acquiring identical cache items recursively is not supported. Key: {0}"; + // Wwe put a special string into cache if value is null, // otherwise our 'Contains()' would always return false, // which is bad if we intentionally wanted to save NULL values. public const string FakeNull = "__[NULL]__"; - private readonly MemoryCache _cache; + private readonly Work _scopeAccessor; + private MemoryCache _cache; private readonly ConcurrentDictionary _keyLocks = new ConcurrentDictionary(); - public MemoryCacheManager() + public MemoryCacheManager(Work scopeAccessor) { - _cache = new MemoryCache("SmartStore"); + _scopeAccessor = scopeAccessor; + _cache = CreateCache(); + } + + private MemoryCache CreateCache() + { + return new MemoryCache("SmartStore"); } public bool IsDistributedCache @@ -32,7 +42,7 @@ public bool IsDistributedCache get { return false; } } - private bool TryGet(string key, out T value) + private bool TryGet(string key, bool independent, out T value) { value = default(T); @@ -40,6 +50,12 @@ private bool TryGet(string key, out T value) if (obj != null) { + // Make the parent scope's entry depend on this + if (!independent) + { + _scopeAccessor.Value.PropagateKey(key); + } + if (obj.Equals(FakeNull)) { return true; @@ -52,65 +68,74 @@ private bool TryGet(string key, out T value) return false; } - public T Get(string key) + public T Get(string key, bool independent = false) { - T value; - TryGet(key, out value); - + TryGet(key, independent, out T value); return value; } - public T Get(string key, Func acquirer, TimeSpan? duration = null) + public T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false) { - T value; - - if (TryGet(key, out value)) + if (TryGet(key, independent, out T value)) { return value; } - lock (String.Intern(key)) + if (_scopeAccessor.Value.HasScope(key)) { - // atomic operation must be outer locked - if (!TryGet(key, out value)) + throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key)); + } + + // Get the (semaphore) locker specific to this key + using (KeyedLock.Lock("cache:" + key, TimeSpan.FromMinutes(1))) + { + // Atomic operation must be outer locked + if (!TryGet(key, independent, out value)) { - value = acquirer(); - Put(key, value, duration); - return value; + using (_scopeAccessor.Value.BeginScope(key)) + { + value = acquirer(); + Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies); + return value; + } } } return value; } - public async Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null) + public async Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false) { - T value; - - if (TryGet(key, out value)) + if (TryGet(key, independent, out T value)) { return value; } - // get the async (semaphore) locker specific to this key - var keyLock = AsyncLock.Acquire(key); + if (_scopeAccessor.Value.HasScope(key)) + { + throw new LockRecursionException(LockRecursionExceptionMessage.FormatInvariant(key)); + } - using (await keyLock.LockAsync()) + // Get the async (semaphore) locker specific to this key + using (await KeyedLock.LockAsync("cache:" + key, TimeSpan.FromMinutes(1))) { - if (!TryGet(key, out value)) + if (!TryGet(key, independent, out value)) { - value = await acquirer().ConfigureAwait(false); - Put(key, value, duration); - return value; + using (_scopeAccessor.Value.BeginScope(key)) + { + value = await acquirer(); + Put(key, value, duration, _scopeAccessor.Value.Current.Dependencies); + return value; + } } } return value; } - public void Put(string key, object value, TimeSpan? duration = null) + public void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null) { - _cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration)); + _cache.Set(key, value ?? FakeNull, GetCacheItemPolicy(duration, dependencies)); } public bool Contains(string key) @@ -135,39 +160,53 @@ public IEnumerable Keys(string pattern) } var wildcard = new Wildcard(pattern, RegexOptions.IgnoreCase); - return keys.Where(x => wildcard.IsMatch(x)); + return keys.Where(x => wildcard.IsMatch(x)).ToArray(); } public int RemoveByPattern(string pattern) { - var keysToRemove = Keys(pattern); - int count = 0; - lock (_cache) { + var keysToRemove = Keys(pattern); + int count = 0; + // lock atomic operation foreach (string key in keysToRemove) { _cache.Remove(key); count++; } - } - return count; + return count; + } } public void Clear() { - RemoveByPattern("*"); - } + // Faster way of clearing cache: https://stackoverflow.com/questions/8043381/how-do-i-clear-a-system-runtime-caching-memorycache + var oldCache = Interlocked.Exchange(ref _cache, CreateCache()); + oldCache.Dispose(); + GC.Collect(); + } - public virtual ISet GetHashSet(string key) + public virtual ISet GetHashSet(string key, Func> acquirer = null) { - var set = Get(key, () => new MemorySet(this)); - return set; + var result = Get(key, () => + { + var set = new MemorySet(this); + var items = acquirer?.Invoke(); + if (items != null) + { + set.AddRange(items); + } + + return set; + }); + + return result; } - private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration) + private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration, IEnumerable dependencies) { var absoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration; @@ -175,16 +214,35 @@ private CacheItemPolicy GetCacheItemPolicy(TimeSpan? duration) { absoluteExpiration = DateTime.UtcNow + duration.Value; } - + var cacheItemPolicy = new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration, SlidingExpiration = ObjectCache.NoSlidingExpiration }; + + if (dependencies != null && dependencies.Any()) + { + // INFO: we can only depend on existing items, otherwise this entry will be removed immediately. + dependencies = dependencies.Where(x => x != null && _cache.Contains(x)); + if (dependencies.Any()) + { + cacheItemPolicy.ChangeMonitors.Add(_cache.CreateCacheEntryChangeMonitor(dependencies)); + } + } + + //cacheItemPolicy.RemovedCallback = OnRemoveEntry; return cacheItemPolicy; } + //private void OnRemoveEntry(CacheEntryRemovedArguments args) + //{ + // if (args.RemovedReason == CacheEntryRemovedReason.ChangeMonitorChanged) + // { + // Debug.WriteLine("MEMCACHE: remove depending entry '{0}'.".FormatInvariant(args.CacheItem.Key)); + // } + //} protected override void OnDispose(bool disposing) { diff --git a/src/Libraries/SmartStore.Core/Caching/MemorySet.cs b/src/Libraries/SmartStore.Core/Caching/MemorySet.cs index 51254daa26..c88cacd703 100644 --- a/src/Libraries/SmartStore.Core/Caching/MemorySet.cs +++ b/src/Libraries/SmartStore.Core/Caching/MemorySet.cs @@ -16,6 +16,11 @@ public MemorySet(ICacheManager cache) _cache = cache; } + public void AddRange(IEnumerable items) + { + base.UnionWith(items); + } + public bool Move(string destinationKey, string item) { if (this.Contains(item)) diff --git a/src/Libraries/SmartStore.Core/Caching/NullCache.cs b/src/Libraries/SmartStore.Core/Caching/NullCache.cs index ae8d19b285..e75abfd91d 100644 --- a/src/Libraries/SmartStore.Core/Caching/NullCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/NullCache.cs @@ -23,12 +23,12 @@ public bool IsDistributedCache get { return false; } } - public T Get(string key) + public T Get(string key, bool independent = false) { return default(T); } - public T Get(string key, Func acquirer, TimeSpan? duration = null) + public T Get(string key, Func acquirer, TimeSpan? duration = null, bool independent = false) { if (acquirer == null) { @@ -37,7 +37,7 @@ public T Get(string key, Func acquirer, TimeSpan? duration = null) return acquirer(); } - public Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null) + public Task GetAsync(string key, Func> acquirer, TimeSpan? duration = null, bool independent = false) { if (acquirer == null) { @@ -47,12 +47,12 @@ public Task GetAsync(string key, Func> acquirer, TimeSpan? duratio } - public ISet GetHashSet(string key) + public ISet GetHashSet(string key, Func> acquirer = null) { return new MemorySet(this); } - public void Put(string key, object value, TimeSpan? duration = null) + public void Put(string key, object value, TimeSpan? duration = null, IEnumerable dependencies = null) { } 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/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs index cb05f3f288..5fb74d2400 100644 --- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs @@ -8,7 +8,7 @@ namespace SmartStore.Core.Caching { - public class RequestCache : IRequestCache + public class RequestCache : DisposableObject, IRequestCache { const string RegionName = "SmartStoreNET:"; @@ -124,5 +124,11 @@ private string BuildKey(string key) { return RegionName + key.EmptyNull(); } + + protected override void OnDispose(bool disposing) + { + if (disposing) + Clear(); + } } } diff --git a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs index d9e6c700b6..86d5b00249 100644 --- a/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs +++ b/src/Libraries/SmartStore.Core/Collections/ConcurrentMultimap.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; -using System.Linq.Expressions; using Newtonsoft.Json; +using SmartStore.Utilities; +using System.Threading; namespace SmartStore.Collections { @@ -15,40 +16,65 @@ namespace SmartStore.Collections /// The type of value. [JsonConverter(typeof(ConcurrentMultiMapConverter))] [Serializable] - public class ConcurrentMultimap : IEnumerable>> + public class ConcurrentMultimap : IEnumerable>> { - private readonly ConcurrentDictionary> _items; - private static readonly Func, IProducerConsumerCollection> _bagCreator = (col) => new ConcurrentBag(col); + private readonly ConcurrentDictionary> _dict; + private readonly Func, ICollection> _collectionCreator; public ConcurrentMultimap() - : this(null, null) + : this(null, null, null) { } public ConcurrentMultimap(IEqualityComparer comparer) - : this(null, comparer) + : this(null, comparer, null) { } - public ConcurrentMultimap(IEnumerable>> items) - : this(items, null) + public ConcurrentMultimap(IEnumerable>> items) + : this(items, null, null) { // for serialization } - public ConcurrentMultimap(IEnumerable>> items, IEqualityComparer comparer) + public ConcurrentMultimap( + IEnumerable>> items, + IEqualityComparer comparer) + : this(items, comparer, null) { - _items = new ConcurrentDictionary>(comparer ?? EqualityComparer.Default); + // for serialization + } + + public ConcurrentMultimap( + IEnumerable>> items, + IEqualityComparer comparer, + Func, ICollection> collectionCreator) + { + _collectionCreator = collectionCreator; + _dict = new ConcurrentDictionary>( + ConvertItems(items), + comparer ?? EqualityComparer.Default); + } + + private IEnumerable>> ConvertItems(IEnumerable>> items) + { + if (items == null) + yield break; - if (items != null) + foreach (var item in items) { - foreach (var kvp in items) - { - _items.TryAdd(kvp.Key, _bagCreator(kvp.Value ?? Enumerable.Empty())); - } + yield return new KeyValuePair>(item.Key, CreateCollection(item.Value)); } } + protected virtual SyncedCollection CreateCollection(IEnumerable values) + { + var creator = _collectionCreator ?? Multimap.DefaultCollectionCreator; + var col = creator(values ?? Enumerable.Empty()); + + return col.AsSynchronized(); + } + /// /// Gets the count of groups/keys. /// @@ -56,7 +82,7 @@ public int Count { get { - return _items.Keys.Count; + return _dict.Keys.Count; } } @@ -67,25 +93,33 @@ public int TotalValueCount { get { - return this._items.Values.Sum(x => x.Count); + return this._dict.Values.Sum(x => x.Count); } } + /// + /// Gets the count of items in the requested group. + /// + public int ValueCount(TKey key) + { + if (_dict.TryGetValue(key, out var col)) + { + return col.Count; + } + + return 0; + } + /// /// Gets the collection of values stored under the specified key. /// /// The key. - public virtual IProducerConsumerCollection this[TKey key] + public virtual SyncedCollection this[TKey key] { get { - IProducerConsumerCollection value; - if (!_items.TryGetValue(key, out value)) - { - _items.TryAdd(key, _bagCreator(Enumerable.Empty())); - } - - return value; + GetOrCreateValues(key, null, out var col); + return col; } } @@ -94,15 +128,15 @@ public virtual IProducerConsumerCollection this[TKey key] /// public virtual ICollection Keys { - get { return _items.Keys; } + get { return _dict.Keys; } } /// - /// Gets the collection of collections of values. + /// Gets all value collections. /// - public virtual ICollection> Values + public virtual ICollection> Values { - get { return _items.Values; } + get { return _dict.Values; } } /// @@ -112,7 +146,10 @@ public virtual ICollection> Values /// The value. public virtual void TryAdd(TKey key, TValue value) { - this[key].TryAdd(value); + if (!GetOrCreateValues(key, new[] { value }, out var col)) + { + col.Add(value); + } } /// @@ -124,18 +161,63 @@ public virtual void TryAddRange(TKey key, IEnumerable values) { Guard.NotNull(values, nameof(values)); - values.Each(x => this[key].TryAdd(x)); + if (!GetOrCreateValues(key, values, out var col)) + { + col.AddRange(values); + } + } + + /// + /// Attempts to remove the specified value for the specified key. + /// + /// The key. + /// The value. + /// True if such a value existed and was removed; otherwise false. + public virtual bool TryRemove(TKey key, TValue value) + { + if (!_dict.ContainsKey(key)) + return false; + + var col = _dict[key]; + var removed = false; + + removed = col.Remove(value); + if (col.Count == 0) + { + _dict.TryRemove(key, out _); + } + + return removed; + } + + /// + /// Attempts to remove a range of values for the specified key. + /// + /// The key. + /// The values to remove from the group. + /// True if at least one item in group has been removed; otherwise false. + public virtual bool TryRemoveRange(TKey key, IEnumerable values) + { + Guard.NotNull(values, nameof(values)); + + int numRemoved = 0; + + if (_dict.TryGetValue(key, out var col)) + { + numRemoved = col.RemoveRange(values); + } + + return numRemoved > 0; } /// - /// Attempts to remove all values for the specified key. + /// Attempts to remove and return all values for the specified key. /// /// The key. /// True if any such values existed; otherwise false. - public virtual bool TryRemoveAll(TKey key) + public virtual bool TryRemoveAll(TKey key, out SyncedCollection collection) { - IProducerConsumerCollection collection; - return _items.TryRemove(key, out collection); + return _dict.TryRemove(key, out collection); } /// @@ -143,7 +225,7 @@ public virtual bool TryRemoveAll(TKey key) /// public virtual void Clear() { - _items.Clear(); + _dict.Clear(); } /// @@ -153,7 +235,7 @@ public virtual void Clear() /// True if the multimap has one or more values for the specified key, otherwise false. public virtual bool ContainsKey(TKey key) { - return _items.ContainsKey(key); + return _dict.ContainsKey(key); } /// @@ -164,7 +246,7 @@ public virtual bool ContainsKey(TKey key) /// True if the multimap contains such a value; otherwise, false. public virtual bool ContainsValue(TKey key, TValue value) { - return _items.ContainsKey(key) && _items[key].Contains(value); + return _dict.ContainsKey(key) && _dict[key].Contains(value); } /// @@ -180,12 +262,26 @@ IEnumerator IEnumerable.GetEnumerator() /// Returns an enumerator that iterates through the multimap. /// /// An object that can be used to iterate through the multimap. - public virtual IEnumerator>> GetEnumerator() + public virtual IEnumerator>> GetEnumerator() { - foreach (var pair in _items) + foreach (var pair in _dict) yield return pair; } + private bool GetOrCreateValues(TKey key, IEnumerable initial, out SyncedCollection col) + { + // Return true when created + var created = false; + + col = _dict.GetOrAdd(key, k => + { + created = true; + return CreateCollection(initial); + }); + + return created; + } + #region Static members public static ConcurrentMultimap CreateFromLookup(ILookup source) diff --git a/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs b/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs deleted file mode 100644 index 6d81d0bc3a..0000000000 --- a/src/Libraries/SmartStore.Core/Collections/MostRecentlyUsedList.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace SmartStore.Collections -{ - public class MostRecentlyUsedList : IEnumerable - { - private readonly int _maxSize; - private readonly List _mru; - - public MostRecentlyUsedList(int maxSize) - { - _maxSize = maxSize; - _mru = new List(); - } - - public MostRecentlyUsedList(IEnumerable collection, int maxSize) - { - _maxSize = maxSize; - _mru = collection.ToList(); - - Normalize(); - } - - public MostRecentlyUsedList(string collection, int maxSize) - { - _maxSize = maxSize; - _mru = collection.SplitSafe(Delimiter).Cast().ToList(); - - Normalize(); - } - - public MostRecentlyUsedList(IEnumerable collection, T newItem, int maxSize) - { - _maxSize = maxSize; - _mru = collection.ToList(); - - Add(newItem); - } - - public MostRecentlyUsedList(string collection, T newItem, int maxSize) - { - _maxSize = maxSize; - _mru = collection.SplitSafe(Delimiter).Cast().ToList(); - - Add(newItem); - } - - public static string Delimiter { get { return ";"; } } - - private void Normalize() - { - if (_maxSize >= 0) - { - while (_mru.Count > _maxSize) - _mru.RemoveAt(_mru.Count - 1); - } - } - - public IEnumerator GetEnumerator() - { - return _mru.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _mru.GetEnumerator(); - } - - public override string ToString() - { - return string.Join(Delimiter, _mru); - } - - public T this[int key] - { - get - { - return _mru[key]; - } - set - { - _mru[key] = value; - } - } - - public int Count { get { return _mru.Count; } } - - public void Add(T item) - { - int i = _mru.IndexOf(item); - if (i > -1) - _mru.RemoveAt(i); - - _mru.Insert(0, item); - - Normalize(); - } - } -} diff --git a/src/Libraries/SmartStore.Core/Collections/MultiMap.cs b/src/Libraries/SmartStore.Core/Collections/MultiMap.cs index 6c8f85fc78..738ffab5fa 100644 --- a/src/Libraries/SmartStore.Core/Collections/MultiMap.cs +++ b/src/Libraries/SmartStore.Core/Collections/MultiMap.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Concurrent; using System.Linq; using System.Linq.Expressions; using Newtonsoft.Json; @@ -17,11 +16,12 @@ namespace SmartStore.Collections [Serializable] public class Multimap : IEnumerable>> { - private readonly IDictionary> _items; + private readonly IDictionary> _dict; private readonly Func, ICollection> _collectionCreator; private readonly bool _isReadonly = false; - private readonly static Func, ICollection> _defaultCollectionCreator = x => new List(x ?? Enumerable.Empty()); + internal readonly static Func, ICollection> DefaultCollectionCreator = + x => new List(x ?? Enumerable.Empty()); public Multimap() : this(EqualityComparer.Default) @@ -30,8 +30,8 @@ public Multimap() public Multimap(IEqualityComparer comparer) { - _items = new Dictionary>(comparer ?? EqualityComparer.Default); - _collectionCreator = _defaultCollectionCreator; + _dict = new Dictionary>(comparer ?? EqualityComparer.Default); + _collectionCreator = DefaultCollectionCreator; } public Multimap(Func, ICollection> collectionCreator) @@ -49,7 +49,7 @@ internal Multimap(IDictionary> dictionary, Func> dictionary, bool isRea { Guard.NotNull(dictionary, nameof(dictionary)); - _items = dictionary; + _dict = dictionary; if (isReadonly && dictionary != null) { @@ -81,21 +81,20 @@ public Multimap(IEnumerable>> items, IEqu // for serialization Guard.NotNull(items, nameof(items)); - _items = new Dictionary>(comparer ?? EqualityComparer.Default); - _collectionCreator = _defaultCollectionCreator; + _dict = new Dictionary>(comparer ?? EqualityComparer.Default); if (items != null) { foreach (var kvp in items) { - _items[kvp.Key] = CreateCollection(kvp.Value); + _dict[kvp.Key] = CreateCollection(kvp.Value); } } } protected virtual ICollection CreateCollection(IEnumerable values) { - return (_collectionCreator ?? _defaultCollectionCreator)(values ?? Enumerable.Empty()); + return (_collectionCreator ?? DefaultCollectionCreator)(values ?? Enumerable.Empty()); } /// @@ -105,7 +104,7 @@ public int Count { get { - return this._items.Keys.Count; + return this._dict.Keys.Count; } } @@ -116,7 +115,7 @@ public int TotalValueCount { get { - return this._items.Values.Sum(x => x.Count); + return this._dict.Values.Sum(x => x.Count); } } @@ -128,15 +127,15 @@ public virtual ICollection this[TKey key] { get { - if (!_items.ContainsKey(key)) + if (!_dict.ContainsKey(key)) { if (!_isReadonly) - _items[key] = CreateCollection(null); + _dict[key] = CreateCollection(null); else return null; } - return _items[key]; + return _dict[key]; } } @@ -145,15 +144,15 @@ public virtual ICollection this[TKey key] /// public virtual ICollection Keys { - get { return _items.Keys; } + get { return _dict.Keys; } } - /// - /// Gets the collection of collections of values. - /// + /// + /// Gets all value collections. + /// public virtual ICollection> Values { - get { return _items.Values; } + get { return _dict.Values; } } public IEnumerable Find(TKey key, Expression> predicate) @@ -161,9 +160,9 @@ public IEnumerable Find(TKey key, Expression> predica Guard.NotNull(key, nameof(key)); Guard.NotNull(predicate, nameof(predicate)); - if (_items.ContainsKey(key)) + if (_dict.ContainsKey(key)) { - return _items[key].Where(predicate.Compile()); + return _dict[key].Where(predicate.Compile()); } return Enumerable.Empty(); @@ -188,7 +187,10 @@ public virtual void Add(TKey key, TValue value) /// The values. public virtual void AddRange(TKey key, IEnumerable values) { - CheckNotReadonly(); + if (values == null || !values.Any()) + return; + + CheckNotReadonly(); this[key].AddRange(values); } @@ -203,12 +205,12 @@ public virtual bool Remove(TKey key, TValue value) { CheckNotReadonly(); - if (!_items.ContainsKey(key)) + if (!_dict.ContainsKey(key)) return false; - bool result = _items[key].Remove(value); - if (_items[key].Count == 0) - _items.Remove(key); + bool result = _dict[key].Remove(value); + if (_dict[key].Count == 0) + _dict.Remove(key); return result; } @@ -221,7 +223,7 @@ public virtual bool Remove(TKey key, TValue value) public virtual bool RemoveAll(TKey key) { CheckNotReadonly(); - return _items.Remove(key); + return _dict.Remove(key); } /// @@ -230,7 +232,7 @@ public virtual bool RemoveAll(TKey key) public virtual void Clear() { CheckNotReadonly(); - _items.Clear(); + _dict.Clear(); } /// @@ -240,7 +242,7 @@ public virtual void Clear() /// True if the multimap has one or more values for the specified key, otherwise false. public virtual bool ContainsKey(TKey key) { - return _items.ContainsKey(key); + return _dict.ContainsKey(key); } /// @@ -251,7 +253,7 @@ public virtual bool ContainsKey(TKey key) /// True if the multimap contains such a value; otherwise, false. public virtual bool ContainsValue(TKey key, TValue value) { - return _items.ContainsKey(key) && _items[key].Contains(value); + return _dict.ContainsKey(key) && _dict[key].Contains(value); } /// @@ -269,7 +271,7 @@ IEnumerator IEnumerable.GetEnumerator() /// An object that can be used to iterate through the multimap. public virtual IEnumerator>> GetEnumerator() { - foreach (KeyValuePair> pair in _items) + foreach (KeyValuePair> pair in _dict) yield return pair; } 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/SyncedCollection.cs b/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs new file mode 100644 index 0000000000..d8100effd8 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/SyncedCollection.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Collections +{ + public sealed class SyncedCollection : ICollection + { + // INFO: Don't call it SynchronizedCollection because of framework dupe. + private readonly ICollection _col; + + public SyncedCollection(ICollection wrappedCollection) + : this(wrappedCollection, new object()) + { + } + + public SyncedCollection(ICollection wrappedCollection, object syncRoot) + { + Guard.NotNull(wrappedCollection, nameof(wrappedCollection)); + Guard.NotNull(syncRoot, nameof(syncRoot)); + + _col = wrappedCollection; + SyncRoot = syncRoot; + } + + public object SyncRoot { get; } + + public bool ReadLockFree { get; set; } + + public void AddRange(IEnumerable collection) + { + lock (SyncRoot) + { + _col.AddRange(collection); + } + } + + public void Insert(int index, T item) + { + if (_col is List list) + { + lock (SyncRoot) + { + list.Insert(index, item); + } + } + + throw new NotSupportedException(); + } + + public void InsertRange(int index, IEnumerable values) + { + if (_col is List list) + { + lock (SyncRoot) + { + list.InsertRange(index, values); + } + } + + throw new NotSupportedException(); + } + + public int RemoveRange(IEnumerable values) + { + int numRemoved = 0; + + lock (SyncRoot) + { + foreach (var value in values) + { + if (_col.Remove(value)) + numRemoved++; + } + } + + return numRemoved; + } + + public void RemoveRange(int index, int count) + { + if (_col is List list) + { + lock (SyncRoot) + { + list.RemoveRange(index, count); + } + } + + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + lock (SyncRoot) + { + if (_col is List list) + { + list.RemoveAt(index); + } + else + { + var item = _col.ElementAtOrDefault(index); + if (item != null) + { + _col.Remove(item); + } + } + } + } + + public T this[int index] + { + get + { + if (ReadLockFree) + { + return _col.ElementAt(index); + } + else + { + lock (SyncRoot) + { + return _col.ElementAt(index); + } + } + } + } + + #region ICollection + + public int Count + { + get + { + if (ReadLockFree) + { + return _col.Count(); + } + else + { + lock (SyncRoot) + { + return _col.Count(); + } + } + } + } + + public bool IsReadOnly => _col.IsReadOnly; + + public void Add(T item) + { + lock (SyncRoot) + { + _col.Add(item); + } + } + + public void Clear() + { + lock (SyncRoot) + { + _col.Clear(); + } + } + + public bool Contains(T item) + { + if (ReadLockFree) + { + return _col.Contains(item); + } + else + { + lock (SyncRoot) + { + return _col.Contains(item); + } + } + } + + public void CopyTo(T[] array, int arrayIndex) + { + lock (SyncRoot) + { + _col.CopyTo(array, arrayIndex); + } + } + + public bool Remove(T item) + { + lock (SyncRoot) + { + return _col.Remove(item); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + if (ReadLockFree) + { + return _col.GetEnumerator(); + } + else + { + lock (SyncRoot) + { + return _col.GetEnumerator(); + } + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNode.cs b/src/Libraries/SmartStore.Core/Collections/TreeNode.cs index e9710fac8c..00034d0778 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; } @@ -226,7 +227,17 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s if (GetPropValue("Metadata", value) is IDictionary dict && dict.Count > 0) { writer.WritePropertyName("Metadata"); - serializer.Serialize(writer, dict); + + var typeNameHandling = serializer.TypeNameHandling; + try + { + serializer.TypeNameHandling = TypeNameHandling.All; + serializer.Serialize(writer, dict); + } + finally + { + serializer.TypeNameHandling = typeNameHandling; + } } // Children diff --git a/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs index 695866869a..8264db12e1 100644 --- a/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs +++ b/src/Libraries/SmartStore.Core/Collections/TreeNodeBase.cs @@ -34,17 +34,16 @@ public object Id } set { + _id = value; + if (_parent != null) { var map = GetIdNodeMap(); - if (_id != value) + if (_id != null && map.ContainsKey(_id)) { - if (_id != null && map.ContainsKey(_id)) - { - // Remove old id from map - map.Remove(_id); - } + // Remove old id from map + map.Remove(_id); } if (value != null) @@ -52,8 +51,6 @@ public object Id map[value] = this; } } - - _id = value; } } @@ -142,7 +139,12 @@ public TMetadata GetMetadata(string key, bool recursive = true) break; } - return (TMetadata)metadata; + if (metadata != null) + { + return (TMetadata)metadata; + } + + return default(TMetadata); } private bool TryGetMetadataForNode(TreeNodeBase node, string key, out object metadata) @@ -203,20 +205,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 +227,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) + { + // 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) { - var map = GetIdNodeMap(); - if (map != 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 +635,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 +666,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 +685,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/Collections/TrimmedBuffer.cs b/src/Libraries/SmartStore.Core/Collections/TrimmedBuffer.cs new file mode 100644 index 0000000000..db54bf8936 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Collections/TrimmedBuffer.cs @@ -0,0 +1,106 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace SmartStore.Collections +{ + public sealed class TrimmedBuffer : ICollection + { + private readonly int _maxSize; + private readonly List _list; + + public TrimmedBuffer(int maxSize) + : this((IEnumerable)null, default(T), maxSize) + { + } + + public TrimmedBuffer(IEnumerable collection, int maxSize) + : this(collection, default(T), maxSize) + { + } + + public TrimmedBuffer(string collection, int maxSize) + : this(collection.NullEmpty()?.Convert>()?.Distinct(), default(T), maxSize) + { + } + + public TrimmedBuffer(string collection, T newItem, int maxSize) + : this(collection.NullEmpty()?.Convert>()?.Distinct(), newItem, maxSize) + { + } + + public TrimmedBuffer(IEnumerable collection, T newItem, int maxSize) + { + Guard.IsTrue(maxSize >= 0, nameof(maxSize)); + + _maxSize = maxSize; + _list = new List(collection ?? Enumerable.Empty()); + + if (newItem != null) + { + Add(newItem); + } + + Trim(); + } + + private void Trim() + { + if (_maxSize >= 0) + { + while (_list.Count > _maxSize) + _list.RemoveAt(_list.Count - 1); + } + } + + public T this[int index] + { + get + { + return _list[index]; + } + set + { + _list[index] = value; + } + } + + public void Add(T item) + { + var i = _list.IndexOf(item); + if (i > -1) + _list.RemoveAt(i); + + _list.Insert(0, item); + + Trim(); + } + + public void Clear() => _list.Clear(); + + public bool Contains(T item) => _list.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + + public bool Remove(T item) => _list.Remove(item); + + public int Count => _list.Count; + + public bool IsReadOnly => false; + + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _list.GetEnumerator(); + } + + public override string ToString() + { + return string.Join(";", _list); + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs index d37ec07015..85419958d3 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastActivator.cs @@ -44,6 +44,8 @@ public object Activate(params object[] parameters) return Invoker(parameters); } + #region Static + /// /// Creates a single fast constructor invoker. The result is not cached. /// @@ -78,8 +80,6 @@ public static Func MakeFastInvoker(ConstructorInfo constructor return lambda.Compile(); } - #region Static - /// /// Creates and caches fast constructor invokers /// @@ -93,8 +93,7 @@ public static FastActivator[] GetActivators(Type type) private static FastActivator[] GetActivatorsCore(Type type) { - FastActivator[] activators; - if (!_activatorsCache.TryGetValue(type, out activators)) + if (!_activatorsCache.TryGetValue(type, out FastActivator[] activators)) { var candidates = GetCandidateConstructors(type); activators = candidates.Select(c => new FastActivator(c)).ToArray(); diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs new file mode 100644 index 0000000000..ba43358e1a --- /dev/null +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastInvoker.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Linq.Expressions; +using System.Collections.Concurrent; +using SmartStore.Utilities; + +namespace SmartStore.ComponentModel +{ + public class FastInvoker + { + private static readonly ConcurrentDictionary _invokersCache = new ConcurrentDictionary(); + + public FastInvoker(MethodInfo methodInfo) + { + Guard.NotNull(methodInfo, nameof(methodInfo)); + + Method = methodInfo; + Invoker = MakeFastInvoker(methodInfo); + ParameterTypes = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray(); + } + + /// + /// Gets the backing . + /// + public MethodInfo Method { get; private set; } + + /// + /// Gets the parameter types from the backing + /// + public Type[] ParameterTypes { get; private set; } + + /// + /// Gets the method invoker. + /// + public Func Invoker { get; private set; } + + /// + /// Invokes the method using the specified parameters. + /// + /// The method invocation result. + public object Invoke(object obj, params object[] parameters) + { + return Invoker(obj, parameters); + } + + #region Static + + /// + /// Creates a single fast method invoker. The result is not cached. + /// + /// Method to create invoker for. + /// The fast method invoker delegate. + public static Func MakeFastInvoker(MethodInfo method) + { + Guard.NotNull(method, nameof(method)); + + var instanceParameterExpression = Expression.Parameter(typeof(object), "instance"); + var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args"); + var index = 0; + + var argumentExtractionExpressions = + method + .GetParameters() + .Select(parameter => + Expression.Convert( + Expression.ArrayAccess( + argumentsParameterExpression, + Expression.Constant(index++) + ), + parameter.ParameterType + ) + ).ToList(); + + var callExpression = method.IsStatic + ? Expression.Call(method, argumentExtractionExpressions) + : Expression.Call( + Expression.Convert( + instanceParameterExpression, + method.DeclaringType + ), + method, + argumentExtractionExpressions + ); + + var endLabel = Expression.Label(typeof(object)); + + var finalExpression = method.ReturnType == typeof(void) + ? (Expression)Expression.Block( + callExpression, + Expression.Return(endLabel, Expression.Constant(null)), + Expression.Label(endLabel, Expression.Constant(null)) + ) + : Expression.Convert(callExpression, typeof(object)); + + var lambdaExpression = Expression.Lambda>( + finalExpression, + instanceParameterExpression, + argumentsParameterExpression + ); + + var lamdba = lambdaExpression.Compile(); + return lamdba; + } + + /// + /// Invokes a method using the specified object and parameter instances. + /// + /// The objectinstance + /// Method name + /// Argument types of the matching method overload (in exact order) + /// Parameter instances to pass to invocation + /// The method invocation result. + public static object Invoke(object obj, string methodName, Type[] parameterTypes, object[] parameters) + { + Guard.NotNull(obj, nameof(obj)); + + FastInvoker invoker; + + if (parameterTypes == null || parameterTypes.Length == 0) + { + invoker = GetInvoker(obj.GetType(), methodName); + } + else + { + invoker = GetInvoker(obj.GetType(), methodName, parameterTypes); + } + + //var hasAnyNullParam = parameters.Any(x => x == null); + //if (hasAnyNullParam) + //{ + // throw new ArgumentException("When invoking a method with parameter instances, no instance must be null.", nameof(parameters)); + //} + + return invoker.Invoke(obj, parameters ?? Array.Empty()); + } + + /// + /// Creates and caches a fast method invoker. + /// + /// Name of method to create an invoker for. + /// Argument types of method to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(string methodName, params Type[] argTypes) + { + return GetInvoker(typeof(T), methodName, argTypes); + } + + /// + /// Creates and caches a fast method invoker. + /// + /// The type to extract fast method invoker for. + /// Name of method to create an invoker for. + /// Argument types of method to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(Type type, string methodName, params Type[] argTypes) + { + Guard.NotNull(type, nameof(type)); + Guard.NotEmpty(methodName, nameof(methodName)); + + var cacheKey = MethodKey.Create(type, methodName, argTypes); + + if (!_invokersCache.TryGetValue(cacheKey, out var invoker)) + { + var method = FindMatchingMethod(type, methodName, argTypes); + + if (method == null) + { + throw new MethodAccessException("Could not find a matching method '{0}' in type {1}.".FormatInvariant(methodName, type)); + } + + invoker = new FastInvoker(method); + _invokersCache.TryAdd(cacheKey, invoker); + } + + return invoker; + } + + /// + /// Creates and caches a fast method invoker. + /// + /// Method info instance to create an invoker for. + /// The fast method invoker. + public static FastInvoker GetInvoker(MethodInfo method) + { + Guard.NotNull(method, nameof(method)); + + var cacheKey = MethodKey.Create(method); + + if (!_invokersCache.TryGetValue(cacheKey, out var invoker)) + { + invoker = new FastInvoker(method); + _invokersCache.TryAdd(cacheKey, invoker); + } + + return invoker; + } + + private static MethodInfo FindMatchingMethod(Type type, string methodName, Type[] argTypes) + { + var method = argTypes == null || argTypes.Length == 0 + ? type.GetMethod(methodName) + : type.GetMethod( + methodName, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + argTypes ?? new Type[0], + null); + + return method; + } + + #endregion + + abstract class MethodKey + { + public override bool Equals(object obj) => + throw new NotImplementedException(); + + public override int GetHashCode() => + throw new NotImplementedException(); + + public static bool operator ==(MethodKey left, MethodKey right) => + object.Equals(left, right); + + public static bool operator !=(MethodKey left, MethodKey right) => + !(left == right); + + internal static MethodKey Create(Type type, string methodName, IEnumerable parameterTypes) + { + return new HashMethodKey(type, methodName, parameterTypes); + } + + internal static MethodKey Create(MethodInfo method) + { + return new MethodInfoKey(method); + } + } + + class HashMethodKey : MethodKey, IEquatable + { + private readonly int _hash; + + public HashMethodKey(Type type, string methodName, IEnumerable parameterTypes) + { + _hash = HashCodeCombiner.Start().Add(type).Add(methodName).Add(parameterTypes).CombinedHash; + } + + public override bool Equals(object obj) => + this.Equals(obj as HashMethodKey); + + public bool Equals(HashMethodKey other) + { + if (other == null) + return false; + + return this._hash == other._hash; + } + + public override int GetHashCode() + { + return _hash; + } + } + + class MethodInfoKey : MethodKey, IEquatable + { + private readonly MethodInfo _method; + + public MethodInfoKey(MethodInfo method) + { + _method = method; + } + + public override bool Equals(object obj) => + this.Equals(obj as MethodInfoKey); + + public bool Equals(MethodInfoKey other) + { + if (other == null) + return false; + + return this._method == other._method; + } + + public override int GetHashCode() + { + return _method.GetHashCode(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs index 4ad5aebee4..7aafa809cd 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs @@ -543,7 +543,7 @@ protected static IDictionary GetVisibleProperties( { return result; } - + // The simple and common case, this is normal POCO object - no need to allocate. var allPropertiesDefinedOnType = true; var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); 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/ComponentModel/TypeConversion/EnumerableConverter.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs index 5687317362..ae60b58488 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/EnumerableConverter.cs @@ -125,7 +125,7 @@ public override object ConvertTo(CultureInfo culture, string format, object valu if (value is IEnumerable enumerable) { - // we don't use string.Join() because it doesn't support invariant culture + // We don't use string.Join() because it doesn't support invariant culture foreach (var token in enumerable) { var str = _elementTypeConverter.ConvertTo(culture, format, token, typeof(string)); diff --git a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs index 299ab07a07..ee6c50e474 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/TypeConversion/TypeConverterFactory.cs @@ -86,7 +86,8 @@ public static ITypeConverter GetConverter(object component) public static ITypeConverter GetConverter(Type type) { - Guard.NotNull(type, nameof(type)); + if (type == null) + throw new ArgumentNullException(nameof(type)); if (_typeConverters.TryGetValue(type, out var converter)) { diff --git a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs index d0ed7563d2..00d96fe9ba 100644 --- a/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs +++ b/src/Libraries/SmartStore.Core/Configuration/JsonPersistAttribute.cs @@ -4,17 +4,15 @@ 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 /// into single properties. /// [AttributeUsage(AttributeTargets.Class)] + [Obsolete] public class JsonPersistAttribute : Attribute { } - } diff --git a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs index 93a7552c50..7d3b433e8e 100644 --- a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs +++ b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs @@ -3,6 +3,7 @@ using System.Linq.Expressions; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace SmartStore.Core.Data { @@ -89,7 +90,12 @@ public int Commit() return _ctx.SaveChanges(); } - public void Dispose() + public async Task CommitAsync() + { + return await _ctx.SaveChangesAsync(); + } + + public void Dispose() { _ctx.AutoDetectChangesEnabled = _autoDetectChangesEnabled; _ctx.ProxyCreationEnabled = _proxyCreationEnabled; diff --git a/src/Libraries/SmartStore.Core/Data/DbQuerySettings.cs b/src/Libraries/SmartStore.Core/Data/DbQuerySettings.cs index d1e22d6965..3e2318e4f0 100644 --- a/src/Libraries/SmartStore.Core/Data/DbQuerySettings.cs +++ b/src/Libraries/SmartStore.Core/Data/DbQuerySettings.cs @@ -4,8 +4,6 @@ namespace SmartStore.Core.Data { public class DbQuerySettings { - private readonly static DbQuerySettings s_default = new DbQuerySettings(false, false); - public DbQuerySettings(bool ignoreAcl, bool ignoreMultiStore) { this.IgnoreAcl = ignoreAcl; @@ -15,9 +13,6 @@ public DbQuerySettings(bool ignoreAcl, bool ignoreMultiStore) public bool IgnoreAcl { get; private set; } public bool IgnoreMultiStore { get; private set; } - public static DbQuerySettings Default - { - get { return s_default; } - } + public static DbQuerySettings Default { get; } = new DbQuerySettings(false, false); } } diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/DbLoadHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DbLoadHook.cs deleted file mode 100644 index b2e9d20731..0000000000 --- a/src/Libraries/SmartStore.Core/Data/Hooks/DbLoadHook.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace SmartStore.Core.Data.Hooks -{ - public abstract class DbLoadHook : IDbLoadHook - { - public virtual void OnLoaded(BaseEntity entity) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs index 656ebc60be..09b74716ac 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/DbSaveHook.cs @@ -40,7 +40,6 @@ public virtual void OnBeforeSaveCompleted() { } - public virtual void OnAfterSave(IHookedEntity entry) { var entity = entry.Entity as TEntity; diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs index 8152d3fd3e..39126f48f8 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/DefaultDbHookHandler.cs @@ -8,8 +8,6 @@ namespace SmartStore.Core.Data.Hooks { public class DefaultDbHookHandler : IDbHookHandler { - private readonly IEnumerable> _hooks; - private readonly IList> _loadHooks; private readonly IList> _saveHooks; private readonly Multimap _hooksRequestCache = new Multimap(); @@ -17,7 +15,6 @@ public class DefaultDbHookHandler : IDbHookHandler // Prevents repetitive hooking of the same entity/state/[pre|post] combination within a single request private readonly HashSet _hookedEntities = new HashSet(); - private static HashSet _importantLoadHookTypes; private static HashSet _importantSaveHookTypes; private readonly static object _lock = new object(); @@ -29,8 +26,6 @@ public class DefaultDbHookHandler : IDbHookHandler public DefaultDbHookHandler(IEnumerable> hooks) { - _hooks = hooks; - _loadHooks = hooks.Where(x => x.Metadata.IsLoadHook == true).ToList(); _saveHooks = hooks.Where(x => x.Metadata.IsLoadHook == false).ToList(); } @@ -38,24 +33,7 @@ public ILogger Logger { get; set; - } - - public bool HasImportantLoadHooks() - { - if (_importantLoadHookTypes == null) - { - lock (_lock) - { - if (_importantLoadHookTypes == null) - { - _importantLoadHookTypes = new HashSet(); - _importantLoadHookTypes.AddRange(_loadHooks.Where(x => x.Metadata.Important).Select(x => x.Metadata.ImplType)); - } - } - } - - return _importantLoadHookTypes.Any(); - } + } = NullLogger.Instance; public bool HasImportantSaveHooks() { @@ -74,41 +52,6 @@ public bool HasImportantSaveHooks() return _importantSaveHookTypes.Any(); } - public IEnumerable TriggerLoadHooks(BaseEntity entity, bool importantHooksOnly) - { - Guard.NotNull(entity, nameof(entity)); - - var processedHooks = new HashSet(); - - if (!_loadHooks.Any() || (importantHooksOnly && !this.HasImportantLoadHooks())) - { - return processedHooks; - } - - var entityType = entity.GetUnproxiedType(); - - var hooks = GetLoadHookInstancesFor(entityType, importantHooksOnly); - foreach (var hook in hooks) - { - // call hook - try - { - hook.OnLoaded(entity); - processedHooks.Add(hook); - } - catch (Exception ex) when (ex is NotImplementedException || ex is NotSupportedException) - { - RegisterVoidHook(hook, entityType, EntityState.Unchanged, HookStage.Load); - } - catch (Exception ex) - { - Logger.ErrorFormat(ex, "LoadHook exception ({0})", hook.GetType().FullName); - } - } - - return processedHooks; - } - public IEnumerable TriggerPreSaveHooks(IEnumerable entries, bool importantHooksOnly, out bool anyStateChanged) { Guard.NotNull(entries, nameof(entries)); @@ -210,17 +153,6 @@ public IEnumerable TriggerPostSaveHooks(IEnumerable return processedHooks; } - private IEnumerable GetLoadHookInstancesFor(Type entityType, bool importantOnly) - { - return GetHookInstancesFor( - entityType, - EntityState.Unchanged, - HookStage.Load, - importantOnly, - _loadHooks, - _importantLoadHookTypes); - } - private IEnumerable GetSaveHookInstancesFor(IHookedEntity entry, HookStage stage, bool importantOnly) { return GetHookInstancesFor( diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs b/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs index 2c07d9e699..f1f81574a2 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/HookMetadata.cs @@ -29,8 +29,7 @@ public static HookMetadata Create(Type hookedType, bool important = false { ImplType = typeof(THook), HookedType = hookedType, - Important = important, - IsLoadHook = typeof(IDbLoadHook).IsAssignableFrom(hookedType) + Important = important }; } } diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs index ed010a5313..9e46634c86 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbHookHandler.cs @@ -6,17 +6,8 @@ namespace SmartStore.Core.Data.Hooks { public interface IDbHookHandler { - bool HasImportantLoadHooks(); bool HasImportantSaveHooks(); - /// - /// Triggers all load hooks for a single entity - /// - /// Whether to trigger only hooks marked with the attribute - /// The loaded entity - /// The list of actually processed hook instances - IEnumerable TriggerLoadHooks(BaseEntity entity, bool importantHooksOnly); - /// /// Triggers all pre action hooks /// @@ -44,21 +35,11 @@ public static IDbHookHandler Instance get { return s_instance; } } - public bool HasImportantLoadHooks() - { - return false; - } - public bool HasImportantSaveHooks() { return false; } - public IEnumerable TriggerLoadHooks(BaseEntity entity, bool importantHooksOnly) - { - return Enumerable.Empty(); - } - public IEnumerable TriggerPreSaveHooks(IEnumerable entries, bool importantHooksOnly, out bool anyStateChanged) { anyStateChanged = false; diff --git a/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs b/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs index 843fe689cc..669ffc9dec 100644 --- a/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs +++ b/src/Libraries/SmartStore.Core/Data/Hooks/IDbLoadHook.cs @@ -5,6 +5,7 @@ namespace SmartStore.Core.Data.Hooks /// /// A hook that is executed right after an entity has been loaded from database and materialized. /// + [Obsolete("Not called anymore for performance reasons.")] public interface IDbLoadHook : IDbHook { void OnLoaded(BaseEntity entity); 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/Data/IQueryableExtensions.cs b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs index b4426b1ec8..d9534c06ea 100644 --- a/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IQueryableExtensions.cs @@ -9,7 +9,6 @@ namespace SmartStore { public static class IQueryableExtensions { - /// /// Instructs the repository to eager load entities that may be in the type's association path. /// @@ -40,6 +39,5 @@ public static IQueryable Expand(this IQueryable query, Expre return query.Include(path); } - } } diff --git a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs index a3d54c9f75..5041bebcdf 100644 --- a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Threading.Tasks; namespace SmartStore.Core.Data { @@ -95,7 +96,57 @@ public static int DeleteAll(this IRepository rs, Expression> return count; } - public static IQueryable Get( + /// + /// Truncates the table asynchronously. + /// + /// Entity type + /// The repository + /// An optional filter + /// + /// false: does not make any attempts to determine dependant entities, just deletes ONLY them (faster). + /// true: loads all entities into the context first and deletes them, along with their dependencies (slower). + /// + /// The total number of affected entities + /// + /// This method turns off auto detection, validation and hooking. + /// + [SuppressMessage("ReSharper", "UnusedVariable")] + public async static Task DeleteAllAsync(this IRepository rs, Expression> predicate = null, bool cascade = false) where T : BaseEntity + { + var count = 0; + + using (var scope = new DbContextScope(ctx: rs.Context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false, autoCommit: false)) + { + var query = rs.Table; + if (predicate != null) + { + query = query.Where(predicate); + } + + if (cascade) + { + var records = query.ToList(); + foreach (var chunk in records.Slice(500)) + { + rs.DeleteRange(chunk.ToList()); + count += await rs.Context.SaveChangesAsync(); + } + } + else + { + var ids = query.Select(x => new { x.Id }).ToList(); + foreach (var chunk in ids.Slice(500)) + { + rs.DeleteRange(chunk.Select(x => x.Id)); + count += await rs.Context.SaveChangesAsync(); + } + } + } + + return count; + } + + public static IQueryable Get( this IRepository rs, Expression> predicate = null, Func, IOrderedQueryable> orderBy = null, diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index 2ced0c35c9..394e9e4e9e 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; @@ -68,7 +68,7 @@ public CatalogSettings() ShowColorSquaresInLists = true; ShowDiscountSign = true; ShowVariantCombinationPriceAdjustment = true; - ShowLinkedAttributeValueImage = true; + ShowLinkedAttributeValueImage = true; EnableDynamicPriceUpdate = true; ShowProductReviewsInProductDetail = true; EnableHtmlTextCollapser = true; @@ -80,6 +80,8 @@ public CatalogSettings() ShowProductsFromSubcategories = true; ApplyTierPricePercentageToAttributePriceAdjustments = false; AllowDifferingEmailAddressForEmailAFriend = false; + AllowAnonymousUsersToEmailAFriend = false; + AllowAnonymousUsersToReviewProduct = false; } /// @@ -127,10 +129,15 @@ public CatalogSettings() /// public bool ShowVariantCombinationPriceAdjustment { get; set; } - /// - /// Gets or sets a value indicating whether to display quantity of linked product at attribute values - /// - public bool ShowLinkedAttributeValueQuantity { get; set; } + /// + /// Indicates whether to show a login note if the user is not authorized to see prices. + /// + public bool ShowLoginForPriceNote { get; set; } + + /// + /// Gets or sets a value indicating whether to display quantity of linked product at attribute values + /// + public bool ShowLinkedAttributeValueQuantity { get; set; } /// /// Gets or sets a value indicating whether to display the image of linked product at attribute values @@ -186,11 +193,21 @@ public CatalogSettings() /// Gets or sets a value indicating whether and where to display a list of subcategories /// public SubCategoryDisplayType SubCategoryDisplayType { get; set; } - - /// - /// Gets or sets a value indicating whether a 'Share button' is enabled - /// - public bool ShowShareButton { get; set; } + + /// + /// An option indicating whether sub pages should display the subcategories + /// + public bool ShowSubCategoriesInSubPages { get; set; } + + /// + /// An option indicating whether sub pages should display the category/manufacturer description + /// + public bool ShowDescriptionInSubPages { get; set; } + + /// + /// Gets or sets a value indicating whether a 'Share button' is enabled + /// + public bool ShowShareButton { get; set; } /// /// Gets or sets a share code (e.g. AddThis button code) @@ -399,15 +416,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 @@ -428,11 +455,16 @@ public CatalogSettings() /// An option indicating whether products on category and manufacturer pages should include featured products as well /// public bool IncludeFeaturedProductsInNormalLists { get; set; } - - /// - /// Gets or sets a value indicating whether tier prices should be displayed with applied discounts (if available) - /// - public bool DisplayTierPricesWithDiscounts { get; set; } + + /// + /// An option indicating whether products on category and manufacturer pages should include featured products in sub pages as well + /// + public bool IncludeFeaturedProductsInSubPages { get; set; } + + /// + /// Gets or sets a value indicating whether tier prices should be displayed with applied discounts (if available) + /// + public bool DisplayTierPricesWithDiscounts { get; set; } /// /// Gets or sets a value indicating whether to ignore discounts (side-wide) diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs index bbe46d5043..d74e7c0c73 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Category.cs @@ -42,6 +42,12 @@ public partial class Category : BaseEntity, ICategoryNode, IAuditable, ISoftDele [DataMember] public string BottomDescription { get; set; } + /// + /// Gets or sets the external link expression. If set, any category menu item will navigate to the specified link. + /// + [DataMember] + public string ExternalLink { get; set; } + /// /// Gets or sets a text displayed in a badge next to the category within menus /// @@ -124,7 +130,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/CategoryNode.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs index 1450373772..c07c9fe69e 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CategoryNode.cs @@ -8,10 +8,10 @@ namespace SmartStore.Core.Domain.Catalog { public interface ICategoryNode : ILocalizedEntity, ISlugSupported, IAclSupported, IStoreMappingSupported { - int Id { get; } int ParentCategoryId { get; } string Name { get; } - string Alias { get; } + string ExternalLink { get; } + string Alias { get; } int? PictureId { get; } bool Published { get; } int DisplayOrder { get; } @@ -26,7 +26,8 @@ public class CategoryNode : ICategoryNode public int Id { get; set; } public int ParentCategoryId { get; set; } public string Name { get; set; } - public string Alias { get; set; } + public string ExternalLink { get; set; } + public string Alias { get; set; } public int? PictureId { get; set; } public bool Published { get; set; } public int DisplayOrder { 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..6f18a1722d 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,30 @@ 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 + { + nameof(Product.AvailableEndDateTimeUtc), + nameof(Product.AvailableStartDateTimeUtc), + nameof(Product.Deleted), + nameof(Product.LowStockActivityId), + nameof(Product.LimitedToStores), + nameof(Product.ManageInventoryMethodId), + nameof(Product.MinStockQuantity), + nameof(Product.Published), + nameof(Product.SubjectToAcl), + nameof(Product.VisibleIndividually) + }; + + public static IReadOnlyCollection GetVisibilityAffectingPropertyNames() + { + return _visibilityAffectingProductProps; + } + + #endregion + + private ICollection _productCategories; private ICollection _productManufacturers; private ICollection _productPictures; private ICollection _productReviews; @@ -30,8 +54,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 +286,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 @@ -686,17 +710,34 @@ public decimal Height /// Gets or sets a value indicating whether the entity is published /// [DataMember] + [Index("IX_Product_Published_Deleted_IsSystemProduct", 1)] public bool Published { get; set; } /// /// Gets or sets a value indicating whether the entity has been deleted /// [Index] - public bool Deleted { get; set; } + [Index("IX_Product_Published_Deleted_IsSystemProduct", 2)] + 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("IX_Product_SystemName_IsSystemProduct", 2)] + [Index("IX_Product_Published_Deleted_IsSystemProduct", 3)] + public bool IsSystemProduct { get; set; } + + /// + /// Gets or sets the product system name. + /// + [DataMember] + [Index("IX_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 +892,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 +1136,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/ProductBundleData.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs index 354720c7ea..60373a2d15 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductBundleData.cs @@ -1,18 +1,10 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Globalization; -using System.IO; -using System.Text; -using System.Xml.Serialization; - -namespace SmartStore.Core.Domain.Catalog +namespace SmartStore.Core.Domain.Catalog { - public partial class ProductBundleItemData + public partial class ProductBundleItemData { public ProductBundleItemData(ProductBundleItem item) { - Item = item; // can be null... test with IsValid + Item = item; // Can be null. } public ProductBundleItem Item { get; private set; } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs index 5cb9acaa5f..ee5adfe594 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductCategory.cs @@ -33,15 +33,17 @@ public partial class ProductCategory : BaseEntity /// [DataMember] public int DisplayOrder { get; set; } - + /// /// Gets the category /// + [DataMember] public virtual Category Category { get; set; } /// /// Gets the product /// + [DataMember] public virtual Product Product { get; set; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs index 087a1f9351..73edaec790 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductManufacturer.cs @@ -37,11 +37,13 @@ public partial class ProductManufacturer : BaseEntity /// /// Gets or sets the manufacturer /// + [DataMember] public virtual Manufacturer Manufacturer { get; set; } /// /// Gets or sets the product /// + [DataMember] public virtual Product Product { get; set; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs index e6b612e382..7d9cd545d1 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductReviewHelpfulness.cs @@ -1,25 +1,24 @@ - using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Catalog { /// - /// Represents a product review helpfulness + /// Represents a product review helpfulness. /// public partial class ProductReviewHelpfulness : CustomerContent { /// - /// Gets or sets the product review identifier + /// Gets or sets the product review identifier. /// public int ProductReviewId { get; set; } /// - /// A value indicating whether a review a helpful + /// A value indicating whether a review is helpful. /// public bool WasHelpful { get; set; } /// - /// Gets the product + /// Gets the product review. /// public virtual ProductReview ProductReview { get; set; } } 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/Catalog/ProductSpecificationAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSpecificationAttribute.cs index 132e767131..eb8595b3af 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSpecificationAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/ProductSpecificationAttribute.cs @@ -46,6 +46,7 @@ public partial class ProductSpecificationAttribute : BaseEntity /// /// Gets or sets the specification attribute option /// + [DataMember] public virtual SpecificationAttributeOption SpecificationAttributeOption { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs index 64827d8ce4..47b5a55cef 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/SpecificationAttributeOption.cs @@ -50,7 +50,7 @@ public partial class SpecificationAttributeOption : BaseEntity, ILocalizedEntity public virtual SpecificationAttribute SpecificationAttribute { get; set; } /// - /// Gets or sets the product specification attribute + /// Gets or sets the product specification attributes /// [DataMember] public virtual ICollection ProductSpecificationAttributes diff --git a/src/Libraries/SmartStore.Core/Domain/Cms/MenuItemRecord.cs b/src/Libraries/SmartStore.Core/Domain/Cms/MenuItemRecord.cs new file mode 100644 index 0000000000..9aab96cc35 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Cms/MenuItemRecord.cs @@ -0,0 +1,117 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using SmartStore.Core.Domain.Localization; + +namespace SmartStore.Core.Domain.Cms +{ + /// + /// Represents a menu item. + /// + [Table("MenuItemRecord")] + public class MenuItemRecord : BaseEntity, ILocalizedEntity + { + /// + /// Gets or sets the menu identifier. + /// + [Required] + public int MenuId { get; set; } + + /// + /// Gets the menu. + /// + [ForeignKey("MenuId")] + public virtual MenuRecord Menu { get; set; } + + /// + /// Gets or sets the parent menu item identifier. 0 if the item has no parent. + /// + [Index("IX_MenuItem_ParentItemId")] + public int ParentItemId { get; set; } + + /// + /// Gets or sets the provider name. + /// + [StringLength(100)] + public string ProviderName { get; set; } + + /// + /// Gets or sets the model. + /// + [MaxLength] + public string Model { get; set; } + + /// + /// Gets or sets the title. + /// + [StringLength(400)] + public string Title { get; set; } + + /// + /// Gets or sets the short description. It is used for the link title attribute. + /// + [StringLength(400)] + public string ShortDescription { get; set; } + + /// + /// Gets or sets permission names. + /// + [MaxLength] + public string PermissionNames { get; set; } + + /// + /// Gets or sets a value indicating whether the menu item is published. + /// + [Index("IX_MenuItem_Published")] + public bool Published { get; set; } = true; + + /// + /// Gets or sets the display order. + /// + [Index("IX_MenuItem_DisplayOrder")] + public int DisplayOrder { get; set; } + + /// + /// Gets or sets a value indicating whether the menu item has a divider or a group header. + /// + public bool BeginGroup { get; set; } + + /// + /// If selected and this menu item has children, the menu will initially appear expanded. + /// + public bool ShowExpanded { get; set; } + + /// + /// Gets or sets the no-follow link attribute. + /// + public bool NoFollow { get; set; } + + /// + /// Gets or sets the blank target link attribute. + /// + public bool NewWindow { get; set; } + + /// + /// Gets or sets fontawesome icon class. + /// + [StringLength(100)] + public string Icon { get; set; } + + /// + /// Gets or sets fontawesome icon style. + /// + [StringLength(10)] + public string Style { get; set; } + + /// + /// Gets or sets HTML id attribute. + /// + [StringLength(100)] + public string HtmlId { get; set; } + + /// + /// Gets or sets the CSS class. + /// + [StringLength(100)] + public string CssClass { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Cms/MenuRecord.cs b/src/Libraries/SmartStore.Core/Domain/Cms/MenuRecord.cs new file mode 100644 index 0000000000..c8ebedf1c1 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Cms/MenuRecord.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Stores; + +namespace SmartStore.Core.Domain.Cms +{ + /// + /// Represents a menu. + /// + [Table("MenuRecord")] + public class MenuRecord : BaseEntity, ILocalizedEntity, IStoreMappingSupported, IAclSupported + { + private ICollection _items; + + /// + /// /// Gets or sets the menu items. + /// + public virtual ICollection Items + { + get { return _items ?? (_items = new HashSet()); } + protected set { _items = value; } + } + + /// + /// Gets or sets the system name. It identifies the menu. + /// + [Required, StringLength(400), Index("IX_Menu_SystemName_IsSystemMenu", Order = 0)] + public string SystemName { get; set; } + + /// + /// Gets or sets the value indicating whether this menu is deleteable by a user. + /// + [Index("IX_Menu_SystemName_IsSystemMenu", Order = 1)] + public bool IsSystemMenu { get; set; } + + /// + /// Gets or sets the menu template name. + /// + [StringLength(400)] + public string Template { get; set; } + + /// + /// Gets or sets the widget zone name. + /// + [StringLength(4000)] + public string WidgetZone { get; set; } + + /// + /// Gets or sets the title. + /// + [StringLength(400)] + public string Title { get; set; } + + /// + /// Gets or sets a value indicating whether the menu is published. + /// + [Index("IX_Menu_Published")] + public bool Published { get; set; } = true; + + /// + /// Gets or sets the order for widget registration. + /// + public int DisplayOrder { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores. + /// + [Index("IX_Menu_LimitedToStores")] + public bool LimitedToStores { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is subject to ACL. + /// + [Index("IX_Menu_SubjectToAcl")] + public bool SubjectToAcl { get; set; } + + /// + /// Helper function to get the comma-separated WidgetZone property as an enumerable of strings. + /// + /// Widget zones. + public IEnumerable GetWidgetZones() + { + if (WidgetZone.IsEmpty()) + { + return Enumerable.Empty(); + } + + return WidgetZone.Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()); + } + } +} 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..523a5dbe52 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -9,7 +9,10 @@ public CommonSettings() UseStoredProceduresIfSupported = true; AutoUpdateEnabled = true; EntityPickerPageSize = 48; - } + MaxScheduleHistoryAgeInDays = 30; + MaxNumberOfScheduleHistoryEntries = 100; + MaxQueuedMessagesAgeInDays = 14; + } public bool UseSystemEmailForContactUsForm { get; set; } @@ -28,5 +31,20 @@ 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; } + + /// + /// Gets or sets the maximum age of sent queued messages (in days). + /// + public int MaxQueuedMessagesAgeInDays { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/Domain/Common/PerformanceSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/PerformanceSettings.cs new file mode 100644 index 0000000000..8acd3065db --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Common/PerformanceSettings.cs @@ -0,0 +1,34 @@ +using System; +using SmartStore.Core.Configuration; + +namespace SmartStore.Core.Domain.Common +{ + public class PerformanceSettings : ISettings + { + /// + /// The number of entries in a single cache segment + /// when greedy loading is disabled. The larger the catalog, + /// the smaller this value should be. We recommend segment + /// size 500 for catalogs smaller than 100.000 items. + /// + public int CacheSegmentSize { get; set; } = 500; + + /// + /// By default only instant search prefetches translations. + /// All other product listings work against the segmented cache. + /// In very large multilingual catalogs (> 500.000) setting this to true + /// can result in higher request performance as well as less + /// resource usage. + /// + public bool AlwaysPrefetchTranslations { get; set; } + + /// + /// By default only instant search prefetches url slugs. + /// All other product listings work against the segmented cache. + /// In very large catalogs (> 500.000) setting this to true + /// can result in higher request performance as well as less + /// resource usage. + /// + public bool AlwaysPrefetchUrlSlugs { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs index 46980652d0..1a45382602 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/SocialSettings.cs @@ -4,50 +4,34 @@ namespace SmartStore.Core.Domain.Common { public class SocialSettings : ISettings { - public SocialSettings() - { - ShowSocialLinksInFooter = true; - FacebookLink = "#"; - GooglePlusLink = "#"; - TwitterLink = "#"; - PinterestLink = "#"; - //YoutubeLink = "#"; - //InstagramLink = "#"; - } - - /// + /// /// Gets or sets the value that determines whether social links should be show in the footer /// - public bool ShowSocialLinksInFooter { get; set; } + public bool ShowSocialLinksInFooter { get; set; } = true; /// /// Gets or sets the facebook link /// - public string FacebookLink { get; set; } - - /// - /// Gets or sets the google plus link - /// - public string GooglePlusLink { get; set; } + public string FacebookLink { get; set; } = "#"; /// /// Gets or sets the twitter link /// - public string TwitterLink { get; set; } + public string TwitterLink { get; set; } = "#"; /// /// Gets or sets the pinterest link /// - public string PinterestLink { get; set; } + public string PinterestLink { get; set; } = "#"; /// /// Gets or sets the youtube link /// - public string YoutubeLink { get; set; } + public string YoutubeLink { get; set; } = "#"; - /// - /// Gets or sets the instagram link - /// - public string InstagramLink { get; set; } - } + /// + /// Gets or sets the instagram link + /// + public string InstagramLink { 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/CustomerLoginType.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerLoginType.cs new file mode 100644 index 0000000000..a9c9479506 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerLoginType.cs @@ -0,0 +1,23 @@ +namespace SmartStore.Core.Domain.Customers +{ + /// + /// Represents the customer login type + /// + public enum CustomerLoginType + { + /// + /// The username will be used to login + /// + Username = 10, + + /// + /// The email will be used to login + /// + Email = 20, + + /// + /// The username or the email address can be used to login + /// + UsernameOrEmail = 30 + } +} 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..20033f0c21 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -6,15 +6,14 @@ public class CustomerSettings : ISettings { public CustomerSettings() { - UsernamesEnabled = true; - CustomerNumberMethod = Customers.CustomerNumberMethod.Disabled; - CustomerNumberVisibility = Customers.CustomerNumberVisibility.None; + CustomerLoginType = CustomerLoginType.UsernameOrEmail; + CustomerNumberMethod = CustomerNumberMethod.Disabled; + CustomerNumberVisibility = CustomerNumberVisibility.None; DefaultPasswordFormat = PasswordFormat.Hashed; HashedPasswordFormat = "SHA1"; PasswordMinLength = 6; UserRegistrationType = UserRegistrationType.Standard; AvatarMaximumSizeBytes = 512000; - DefaultAvatarEnabled = true; CustomerNameFormat = CustomerNameFormat.ShowFirstName; CustomerNameFormatMaxLength = 64; GenderEnabled = true; @@ -23,14 +22,14 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = true; - StoreLastIpAddress = true; - DisplayPrivacyAgreementOnContactUs = false; + FirstNameRequired = false; + LastNameRequired = false; } - - /// - /// Gets or sets a value indicating whether usernames are used instead of emails + + /// + /// Gets or sets a value indicating the customer login type /// - public bool UsernamesEnabled { get; set; } + public CustomerLoginType CustomerLoginType { get; set; } /// /// Gets or sets the customer number method @@ -82,11 +81,6 @@ public CustomerSettings() /// public int AvatarMaximumSizeBytes { get; set; } - /// - /// Gets or sets a value indicating whether to display default user avatar. - /// - public bool DefaultAvatarEnabled { get; set; } - /// /// Gets or sets a value indicating whether customers location is shown /// @@ -151,16 +145,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 +158,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 @@ -261,9 +255,6 @@ public CustomerSettings() #endregion - public string PrefillLoginUsername { get; set; } - public string PrefillLoginPwd { get; set; } - /// /// Identifier of a customer role that new registered customers will be assigned to /// 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/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index c795d66377..4bd1648beb 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -14,12 +14,22 @@ public enum ExportEntityType Order, NewsLetterSubscription, ShoppingCartItem - } - - /// - /// Supported deployment types - /// - public enum ExportDeploymentType + } + + /// + /// Supported related entity types (data without own export provider or importer). + /// + public enum RelatedEntityType + { + TierPrice = 0, + ProductVariantAttributeValue, + ProductVariantAttributeCombination + } + + /// + /// Supported deployment types + /// + public enum ExportDeploymentType { FileSystem = 0, Email, @@ -137,7 +147,12 @@ public enum ExportFeatures /// /// Whether to export attribute combinations as products including parent product. Only effective with CanProjectAttributeCombinations. /// - UsesAttributeCombinationParent = 1 << 14 + UsesAttributeCombinationParent = 1 << 14, + + /// + /// Whether to provide extra data units for related data + /// + UsesRelatedDataUnits = 1 << 15 } /// 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/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index 3161d1ea94..3d75cf06ef 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Core.Domain { - public class ExportProfile : BaseEntity, ICloneable + public class ExportProfile : BaseEntity, ICloneable { private ICollection _deployments; @@ -52,10 +51,15 @@ public ExportProfile() /// public bool Enabled { get; set; } - /// - /// The scheduling task identifier - /// - public int SchedulingTaskId { get; set; } + /// + /// Whether the export related data + /// + public bool ExportRelatedData { get; set; } + + /// + /// The scheduling task identifier + /// + public int SchedulingTaskId { get; set; } /// /// XML with filtering information diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs index 1139496b4a..4322a88f2f 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ImportProfile.cs @@ -60,10 +60,15 @@ public ImportEntityType EntityType /// public bool Enabled { get; set; } - /// - /// Number of records to bypass - /// - public int Skip { get; set; } + /// + /// Whether the import related data + /// + public bool ImportRelatedData { get; set; } + + /// + /// Number of records to bypass + /// + public int Skip { get; set; } /// /// Maximum number of records to return 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/Directory/QuantityUnit.cs b/src/Libraries/SmartStore.Core/Domain/Directory/QuantityUnit.cs index f10f0543c3..4e671a5384 100644 --- a/src/Libraries/SmartStore.Core/Domain/Directory/QuantityUnit.cs +++ b/src/Libraries/SmartStore.Core/Domain/Directory/QuantityUnit.cs @@ -1,6 +1,5 @@ -using System; -using SmartStore.Core.Domain.Localization; using System.Runtime.Serialization; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Core.Domain.Directory { @@ -16,6 +15,12 @@ public partial class QuantityUnit : BaseEntity, ILocalizedEntity [DataMember] public string Name { get; set; } + /// + /// Gets or sets the name plural. + /// + [DataMember] + public string NamePlural { get; set; } + /// /// Gets or sets the description /// @@ -38,7 +43,6 @@ public partial class QuantityUnit : BaseEntity, ILocalizedEntity /// Gets or sets the default quantity unit /// [DataMember] - public bool IsDefault { get; set; } - + public bool IsDefault { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs b/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs index d0f24134ae..f7b116da84 100644 --- a/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs +++ b/src/Libraries/SmartStore.Core/Domain/Discounts/Discount.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Runtime.Serialization; using SmartStore.Core.Domain.Catalog; @@ -9,6 +10,7 @@ namespace SmartStore.Core.Domain.Discounts /// Represents a discount /// [DataContract] + [DebuggerDisplay("{Name} - {DiscountType}")] public partial class Discount : BaseEntity { private ICollection _discountRequirements; diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs index 0b268be1f1..b4cedb8d01 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/Forum.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Seo; @@ -12,6 +13,7 @@ public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupp /// /// Gets or sets the forum group identifier /// + [Index("IX_ForumGroupId_DisplayOrder", Order = 0)] public int ForumGroupId { get; set; } /// @@ -57,6 +59,7 @@ public partial class Forum : BaseEntity, IAuditable, ILocalizedEntity, ISlugSupp /// /// Gets or sets the display order /// + [Index("IX_ForumGroupId_DisplayOrder", Order = 1)] public int DisplayOrder { get; set; } /// 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/ForumGroup.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs index d68dceb19c..ae83f4f8f9 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumGroup.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; @@ -9,7 +11,7 @@ namespace SmartStore.Core.Domain.Forums /// /// Represents a forum group /// - public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, ILocalizedEntity, ISlugSupported + public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported, IAclSupported, ILocalizedEntity, ISlugSupported { private ICollection _forums; @@ -26,6 +28,7 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// /// Gets or sets the display order /// + [Index] public int DisplayOrder { get; set; } /// @@ -38,10 +41,17 @@ public partial class ForumGroup : BaseEntity, IAuditable, IStoreMappingSupported /// public DateTime UpdatedOnUtc { get; set; } - /// - /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores - /// - public bool LimitedToStores { get; set; } + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + [Index] + public bool LimitedToStores { get; set; } + + /// + /// Gets or sets a value indicating whether the entity is subject to ACL + /// + [Index] + public bool SubjectToAcl { get; set; } /// /// Gets or sets the collection of Forums diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs index 087c6205a2..e65c80573d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPost.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Forums @@ -8,6 +10,8 @@ namespace SmartStore.Core.Domain.Forums /// public partial class ForumPost : BaseEntity, IAuditable { + private ICollection _forumPostVotes; + /// /// Gets or sets the forum topic identifier /// @@ -31,6 +35,7 @@ public partial class ForumPost : BaseEntity, IAuditable /// /// Gets or sets the date and time of instance creation /// + [Index] public DateTime CreatedOnUtc { get; set; } /// @@ -38,6 +43,12 @@ public partial class ForumPost : BaseEntity, IAuditable /// public DateTime UpdatedOnUtc { get; set; } + /// + /// Gets or sets a value indicating whether the entity is published + /// + [Index] + public bool Published { get; set; } + /// /// Gets the topic /// @@ -48,5 +59,13 @@ public partial class ForumPost : BaseEntity, IAuditable /// public virtual Customer Customer { get; set; } + /// + /// Forum post votes. + /// + public virtual ICollection ForumPostVotes + { + get { return _forumPostVotes ?? (_forumPostVotes = new HashSet()); } + protected set { _forumPostVotes = value; } + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs new file mode 100644 index 0000000000..1799d22dd3 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumPostVote.cs @@ -0,0 +1,25 @@ +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Core.Domain.Forums +{ + /// + /// Represents a vote for a forum post. + /// + public partial class ForumPostVote : CustomerContent + { + /// + /// Forum post identifier. + /// + public int ForumPostId { get; set; } + + /// + /// A value indicating whether the customer voted for or against a forum post. + /// + public bool Vote { get; set; } + + /// + /// Forum post entity. + /// + public virtual ForumPost ForumPost { get; set; } + } +} 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..86a2b5e423 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumSettings.cs @@ -11,23 +11,24 @@ public ForumSettings() TopicSubjectMaxLength = 450; PostMaxLength = 4000; StrippedTopicMaxLength = 45; - TopicsPageSize = 10; - PostsPageSize = 10; - SearchResultsPageSize = 10; - LatestCustomerPostsPageSize = 10; - ShowCustomersPostCount = true; + TopicsPageSize = 20; + PostsPageSize = 20; + ForumFeedCount = 20; + SearchResultsPageSize = 20; + LatestCustomerPostsPageSize = 20; + AllowSorting = true; + ShowCustomersPostCount = true; ForumEditor = EditorType.BBCodeEditor; SignaturesEnabled = true; - PrivateMessagesPageSize = 10; - ForumSubscriptionsPageSize = 10; + PrivateMessagesPageSize = 20; + ForumSubscriptionsPageSize = 20; PMSubjectMaxLength = 450; PMTextMaxLength = 4000; HomePageActiveDiscussionsTopicCount = 5; ActiveDiscussionsPageTopicCount = 50; ActiveDiscussionsFeedCount = 25; - ForumFeedCount = 10; - ForumSearchTermMinimumLength = 3; - } + AllowCustomersToVoteOnPosts = true; + } /// /// Gets or sets a value indicating whether forums are enabled @@ -64,6 +65,16 @@ public ForumSettings() /// public bool AllowCustomersToDeletePosts { get; set; } + /// + /// Gets or sets a value indicating whether customer can vote on posts + /// + public bool AllowCustomersToVoteOnPosts { get; set; } + + /// + /// Gets or sets a value indicating whether guests are allowed to vote on posts + /// + public bool AllowGuestsToVoteOnPosts { get; set; } + /// /// Gets or sets maximum length of topic subject /// @@ -90,14 +101,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 +194,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..63d6134530 100644 --- a/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Forums/ForumTopic.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using SmartStore.Core.Domain.Customers; namespace SmartStore.Core.Domain.Forums @@ -11,6 +12,7 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the forum identifier /// + [Index("IX_ForumId_Published", Order = 0)] public int ForumId { get; set; } /// @@ -21,16 +23,19 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the topic type identifier /// + [Index("IX_TopicTypeId_LastPostTime", Order = 0)] public int TopicTypeId { get; set; } /// /// Gets or sets the subject /// + [Index] public string Subject { get; set; } /// /// Gets or sets the number of posts /// + [Index] public int NumPosts { get; set; } /// @@ -38,6 +43,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 /// @@ -51,11 +62,13 @@ public partial class ForumTopic : BaseEntity, IAuditable /// /// Gets or sets the last post date and time /// + [Index("IX_TopicTypeId_LastPostTime", Order = 1)] public DateTime? LastPostTime { get; set; } /// /// Gets or sets the date and time of instance creation /// + [Index] public DateTime CreatedOnUtc { get; set; } /// @@ -63,6 +76,12 @@ public partial class ForumTopic : BaseEntity, IAuditable /// public DateTime UpdatedOnUtc { get; set; } + /// + /// Gets or sets a value indicating whether the entity is published + /// + [Index("IX_ForumId_Published", Order = 1)] + public bool Published { get; set; } + /// /// Gets or sets the forum topic type /// @@ -95,10 +114,12 @@ public int NumReplies { get { - int result = 0; if (NumPosts > 0) - result = NumPosts - 1; - return result; + { + return NumPosts - 1; + } + + return 0; } } } 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/ILocalizedEntity.cs b/src/Libraries/SmartStore.Core/Domain/Localization/ILocalizedEntity.cs index 9a873cc70b..5f022ea319 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/ILocalizedEntity.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/ILocalizedEntity.cs @@ -6,5 +6,6 @@ namespace SmartStore.Core.Domain.Localization /// public interface ILocalizedEntity { + int Id { get; } } } 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/Localization/LocalizedProperty.cs b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedProperty.cs index f0d0406fb4..6996cf8549 100644 --- a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedProperty.cs +++ b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedProperty.cs @@ -28,6 +28,7 @@ public partial class LocalizedProperty : BaseEntity /// [DataMember] [Index("IX_LocalizedProperty_Compound", Order = 3)] + [Index("IX_LocalizedProperty_LocaleKeyGroup")] public string LocaleKeyGroup { get; set; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedPropertyCollection.cs b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedPropertyCollection.cs new file mode 100644 index 0000000000..9cd5549e0f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Localization/LocalizedPropertyCollection.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Globalization; + +namespace SmartStore.Core.Domain.Localization +{ + public class LocalizedPropertyCollection : IReadOnlyCollection + { + private readonly string _keyGroup; + private readonly IDictionary _dict; + private HashSet _requestedSet; + + public LocalizedPropertyCollection(string keyGroup, int[] requestedSet, IEnumerable items) + { + Guard.NotEmpty(keyGroup, nameof(keyGroup)); + Guard.NotNull(items, nameof(items)); + + _keyGroup = keyGroup; + _dict = items.ToDictionarySafe(x => CreateKey(x.LocaleKey, x.EntityId, x.LanguageId), StringComparer.OrdinalIgnoreCase); + + if (requestedSet != null && requestedSet.Length > 0) + { + _requestedSet = new HashSet(requestedSet); + } + } + + public void MergeWith(LocalizedPropertyCollection other) + { + Guard.NotNull(other, nameof(other)); + + if (!this._keyGroup.IsCaseInsensitiveEqual(other._keyGroup)) + { + throw new InvalidOperationException("Expected keygroup '{0}', but was '{1}'".FormatInvariant(this._keyGroup, other._keyGroup)); + } + + // Merge dictionary + other._dict.Merge(this._dict, true); + + // Merge requested set (entity ids) + if (this._requestedSet != null) + { + if (other._requestedSet == null) + { + other._requestedSet = new HashSet(this._requestedSet); + } + else + { + other._requestedSet.AddRange(this._requestedSet); + } + } + } + + public string GetValue(int languageId, int entityId, string localeKey) + { + return Find(languageId, entityId, localeKey)?.LocaleValue; + } + + public LocalizedProperty Find(int languageId, int entityId, string localeKey) + { + var item = _dict.Get(CreateKey(localeKey, entityId, languageId)); + + if (item == null && (_requestedSet == null || _requestedSet.Contains(entityId))) + { + // Although the item does not exist in the local dictionary it has been requested + // from the database, which means it does not exist in the db either. + // Avoid the upcoming roundtrip. + return new LocalizedProperty + { + LocaleKeyGroup = _keyGroup, + EntityId = entityId, + LanguageId = languageId, + LocaleKey = localeKey + }; + } + + return item; + } + + private string CreateKey(string localeKey, int entityId, int languageId) + { + return string.Concat(localeKey, "-", entityId.ToString(CultureInfo.InvariantCulture), "-", languageId); + } + + public int Count => _dict.Values.Count; + public IEnumerator GetEnumerator() => _dict.Values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _dict.Values.GetEnumerator(); + } +} 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/Media/PictureType.cs b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs index 84d245f3be..94eb05184c 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/PictureType.cs @@ -6,14 +6,11 @@ namespace SmartStore.Core.Domain.Media public enum FallbackPictureType { NoFallback = 0, + /// /// Entities (products, categories, manufacturers) /// Entity = 1, - /// - /// Avatar - /// - Avatar = 10, } public enum ThumbnailScaleMode 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/PaymentSettings.cs b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs index 9ba68a9c34..7aad6a5667 100644 --- a/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Payments/PaymentSettings.cs @@ -36,5 +36,10 @@ public PaymentSettings() /// 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..9d1d5f5bce 100644 --- a/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Seo/SeoSettings.cs @@ -81,7 +81,7 @@ public SeoSettings() public string SeoNameCharConversion { get; set; } public bool CanonicalUrlsEnabled { get; set; } - public CanonicalHostNameRule CanonicalHostNameRule { get; set; } + public CanonicalHostNameRule CanonicalHostNameRule { get; set; } /// /// Slugs (sename) reserved for some other needs @@ -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/Seo/UrlRecordCollection.cs b/src/Libraries/SmartStore.Core/Domain/Seo/UrlRecordCollection.cs new file mode 100644 index 0000000000..733e800f7a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Seo/UrlRecordCollection.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; + +namespace SmartStore.Core.Domain.Seo +{ + public class UrlRecordCollection : IReadOnlyCollection + { + private readonly string _entityName; + private readonly IDictionary _dict; + private HashSet _requestedSet; + + public UrlRecordCollection(string entityName, int[] requestedSet, IEnumerable items) + { + Guard.NotEmpty(entityName, nameof(entityName)); + Guard.NotNull(items, nameof(items)); + + _entityName = entityName; + _dict = items.ToDictionarySafe(x => CreateKey(x.EntityId, x.LanguageId)); + + if (requestedSet != null && requestedSet.Length > 0) + { + _requestedSet = new HashSet(requestedSet); + } + } + + public void MergeWith(UrlRecordCollection other) + { + Guard.NotNull(other, nameof(other)); + + if (!this._entityName.IsCaseInsensitiveEqual(other._entityName)) + { + throw new InvalidOperationException("Expected group '{0}', but was '{1}'".FormatInvariant(this._entityName, other._entityName)); + } + + // Merge dictionary + other._dict.Merge(this._dict, true); + + // Merge requested set (entity ids) + if (this._requestedSet != null) + { + if (other._requestedSet == null) + { + other._requestedSet = new HashSet(this._requestedSet); + } + else + { + other._requestedSet.AddRange(this._requestedSet); + } + } + } + + public string GetSlug(int languageId, int entityId, bool returnDefaultValue = true) + { + var slug = Find(languageId, entityId)?.Slug; + + if (returnDefaultValue && languageId != 0 && string.IsNullOrEmpty(slug)) + { + slug = Find(0, entityId)?.Slug; + } + + return slug; + } + + public UrlRecord Find(int languageId, int entityId) + { + var item = _dict.Get(CreateKey(entityId, languageId)); + + if (item == null && (_requestedSet == null || _requestedSet.Contains(entityId))) + { + // Although the item does not exist in the local dictionary it has been requested + // from the database, which means it does not exist in the db either. + // Avoid the upcoming roundtrip. + return new UrlRecord + { + EntityName = _entityName, + EntityId = entityId, + LanguageId = languageId + }; + } + + return item; + } + + private string CreateKey(int entityId, int languageId) + { + return string.Concat(entityId.ToString(CultureInfo.InvariantCulture), "-", languageId); + } + + public int Count => _dict.Values.Count; + public IEnumerator GetEnumerator() => _dict.Values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _dict.Values.GetEnumerator(); + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs b/src/Libraries/SmartStore.Core/Domain/Stores/Store.cs index da3f0315cd..d9325aa60a 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 /// @@ -91,7 +97,7 @@ public partial class Store : BaseEntity /// /// Gets the security mode for the store /// - public HttpSecurityMode GetSecurityMode(bool? useSsl = null) + public virtual HttpSecurityMode GetSecurityMode(bool? useSsl = null) { if (useSsl ?? SslEnabled) { diff --git a/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs b/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs index 3273ab21da..1185f4b399 100644 --- a/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs +++ b/src/Libraries/SmartStore.Core/Domain/Stores/StoreExtensions.cs @@ -7,42 +7,44 @@ namespace SmartStore.Core.Domain.Stores public static class StoreExtensions { /// - /// Parse comma-separated Hosts + /// Indicates whether a store contains a specified host /// /// Store - /// Comma-separated hosts - public static string[] ParseHostValues(this Store store) + /// Host + /// true - contains, false - no + public static bool ContainsHostValue(this Store store, string host) { - if (store == null) - throw new ArgumentNullException("store"); + Guard.NotNull(store, nameof(store)); - var parsedValues = new List(); - if (!string.IsNullOrEmpty(store.Hosts)) + if (string.IsNullOrEmpty(host)) { - var hosts = store.Hosts.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - parsedValues.AddRange(hosts.Select(host => host.Trim()).Where(tmp => !string.IsNullOrEmpty(tmp))); - } + return false; + } - return parsedValues.ToArray(); + var contains = store.ParseHostValues() + .FirstOrDefault(x => x.Equals(host, StringComparison.InvariantCultureIgnoreCase)) != null; + return contains; } /// - /// Indicates whether a store contains a specified host + /// Parse comma-separated hosts /// /// Store - /// Host - /// true - contains, false - no - public static bool ContainsHostValue(this Store store, string host) + /// Comma-separated hosts + public static string[] ParseHostValues(this Store store) { - if (store == null) - throw new ArgumentNullException("store"); + Guard.NotNull(store, nameof(store)); - if (String.IsNullOrEmpty(host)) - return false; + if (string.IsNullOrWhiteSpace(store.Hosts)) + { + return Array.Empty(); + } - var contains = store.ParseHostValues() - .FirstOrDefault(x => x.Equals(host, StringComparison.InvariantCultureIgnoreCase)) != null; - return contains; + return store.Hosts + .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(host => host.Trim()) + .Where(host => !string.IsNullOrWhiteSpace(host)) + .ToArray(); } } } diff --git a/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs b/src/Libraries/SmartStore.Core/Domain/Tasks/ScheduleTask.cs index ad6dc64ea8..f516fc53da 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,66 +47,66 @@ 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(); + var task = new ScheduleTask + { + Name = Name, + Alias = Alias, + CronExpression = CronExpression, + Type = Type, + Enabled = Enabled, + StopOnError = StopOnError, + NextRunUtc = NextRunUtc, + IsHidden = IsHidden, + RunPerMachine = RunPerMachine + }; + + return task; } object ICloneable.Clone() { - return this.MemberwiseClone(); + return this.Clone(); } } } 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/Tax/TaxSettings.cs b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs index 1d79ba018b..28fd36b269 100644 --- a/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Tax/TaxSettings.cs @@ -12,7 +12,8 @@ public TaxSettings() DisplayTaxSuffix = true; PricesIncludeTax = true; EuVatAllowVatExemption = true; - ShowLegalHintsInFooter = true; + ShippingPriceIncludesTax = true; + ShowLegalHintsInFooter = true; ShowLegalHintsInProductDetails = true; ShowLegalHintsInProductList = true; AuxiliaryServicesTaxingType = AuxiliaryServicesTaxType.SpecifiedTaxCategory; diff --git a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs index fa5ef2b93a..d05d74c975 100644 --- a/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs +++ b/src/Libraries/SmartStore.Core/Domain/Topics/Topic.cs @@ -1,111 +1,174 @@ 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 { + 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 name + /// Gets or sets the html id /// - public string SystemName { get; set; } + [DataMember] + public string HtmlId { get; set; } /// - /// Gets or sets the value indicating whether this topic is deleteable by a user + /// Gets or sets the body css class /// - public bool IsSystemTopic { get; set; } + [DataMember] + public string BodyCssClass { get; set; } /// /// Gets or sets the value indicating whether this topic should be included in sitemap /// - public bool IncludeInSitemap { get; set; } + [DataMember] + 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 value indicating whether this topic is password protected + /// + [DataMember] + public bool IsPasswordProtected { get; set; } - /// - /// Gets or sets the password - /// - public string Password { get; set; } + /// + /// Gets or sets the password + /// + [DataMember] + public string Password { get; set; } - /// - /// Gets or sets the title - /// - public string Title { get; set; } + /// + /// Gets or sets the title + /// + [DataMember] + public string Title { get; set; } - /// - /// Gets or sets the body - /// - public string Body { get; set; } + /// + /// Gets or sets the short title (for links) + /// + [DataMember] + public string ShortTitle { get; set; } - /// - /// Gets or sets the meta keywords - /// - public string MetaKeywords { get; set; } + /// + /// Gets or sets the intro + /// + [DataMember] + public string Intro { get; set; } - /// - /// Gets or sets the meta description - /// - public string MetaDescription { get; set; } + /// + /// Gets or sets the body + /// + [DataMember] + public string Body { get; set; } - /// - /// Gets or sets the meta title - /// - public string MetaTitle { 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 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 - /// - public bool WidgetBordered { 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) - /// - public int Priority { get; set; } + /// + /// Gets or sets the sort order (relevant for widgets) + /// + [DataMember] + public int Priority { get; set; } - /// - /// Gets or sets the title tag - /// - public string TitleTag { get; set; } + /// + /// Gets or sets the title tag + /// + [DataMember] + 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 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/Email/DefaultEmailSender.cs b/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs index 15fc63095f..5492f4631f 100644 --- a/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs +++ b/src/Libraries/SmartStore.Core/Email/DefaultEmailSender.cs @@ -83,20 +83,19 @@ public void SendEmail(SmtpContext context, EmailMessage message) } } - public Task SendEmailAsync(SmtpContext context, EmailMessage message) + public async Task SendEmailAsync(SmtpContext context, EmailMessage message) { Guard.NotNull(context, nameof(context)); Guard.NotNull(message, nameof(message)); - var client = context.ToSmtpClient(); - ApplySettings(client); - var msg = this.BuildMailMessage(message); - - return client.SendMailAsync(msg).ContinueWith(t => + using (var msg = this.BuildMailMessage(message)) { - client.Dispose(); - msg.Dispose(); - }); + using (var client = context.ToSmtpClient()) + { + ApplySettings(client); + await client.SendMailAsync(msg); + } + } } private void ApplySettings(SmtpClient client) diff --git a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs index c3745c1e38..e1fde3c68a 100644 --- a/src/Libraries/SmartStore.Core/Email/SmtpContext.cs +++ b/src/Libraries/SmartStore.Core/Email/SmtpContext.cs @@ -81,7 +81,8 @@ public SmtpClient ToSmtpClient() } else { - smtpClient.Credentials = new NetworkCredential(this.Username, this.Password); + if (!String.IsNullOrEmpty(this.Username)) + smtpClient.Credentials = new NetworkCredential(this.Username, this.Password); } return smtpClient; diff --git a/src/Libraries/SmartStore.Core/Events/AmbigousConsumerException.cs b/src/Libraries/SmartStore.Core/Events/AmbigousConsumerException.cs new file mode 100644 index 0000000000..18b72d94da --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/AmbigousConsumerException.cs @@ -0,0 +1,13 @@ +using System; +using System.Reflection; + +namespace SmartStore.Core.Events +{ + public sealed class AmbigousConsumerException : Exception + { + public AmbigousConsumerException(MethodInfo method1, MethodInfo method2) + : base("Ambigous consumer methods detected in '{0}'. Method 1: '{1}', Method 2: '{2}'.".FormatInvariant(method1.DeclaringType.FullName, method1.ToString(), method2.ToString())) + { + } + } +} diff --git a/src/Libraries/SmartStore.Core/Events/AsyncConsumerAttribute.cs b/src/Libraries/SmartStore.Core/Events/AsyncConsumerAttribute.cs deleted file mode 100644 index 4a33b18f31..0000000000 --- a/src/Libraries/SmartStore.Core/Events/AsyncConsumerAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace SmartStore.Core.Events -{ - [AttributeUsage(AttributeTargets.Class, Inherited=true, AllowMultiple=false)] - public class AsyncConsumerAttribute : Attribute - { - } -} diff --git a/src/Libraries/SmartStore.Core/Events/ConsumeContext.cs b/src/Libraries/SmartStore.Core/Events/ConsumeContext.cs new file mode 100644 index 0000000000..d52607845a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/ConsumeContext.cs @@ -0,0 +1,23 @@ +using System; + +namespace SmartStore.Core.Events +{ + /// + /// Wrapper/Envelope for event message objects. For now, only wraps + /// the message. There may be other context data in future. + /// + /// Type of message. + public class ConsumeContext + { + public ConsumeContext(TMessage message) + { + Message = message; + } + + public TMessage Message + { + get; + private set; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Events/ConsumerDescriptor.cs b/src/Libraries/SmartStore.Core/Events/ConsumerDescriptor.cs new file mode 100644 index 0000000000..f990c1e61c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/ConsumerDescriptor.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using SmartStore.ComponentModel; +using SmartStore.Core.Plugins; + +namespace SmartStore.Core.Events +{ + /// + /// Contains metadata about a consumer / event handler method. + /// + public class ConsumerDescriptor + { + public ConsumerDescriptor() + { + } + + public ConsumerDescriptor(EventConsumerMetadata metadata) + { + ContainerType = metadata.ContainerType; + PluginDescriptor = metadata.PluginDescriptor; + } + + public bool WithEnvelope { get; set; } + public bool IsAsync { get; set; } + public bool FireForget { get; set; } + public PluginDescriptor PluginDescriptor { get; set; } + + public Type MessageType { get; set; } + public Type ContainerType { get; set; } + + public MethodInfo Method { get; set; } + public ParameterInfo MessageParameter { get; set; } + + /// + /// All method parameters except + /// or empty array if is the only parameter. + /// + public ParameterInfo[] Parameters { get; set; } + + public override string ToString() + { + return "MessageType: {0} - Consumer: {1}.{2}".FormatCurrent(MessageType.Name, ContainerType.FullName, Method.Name); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Events/ConsumerInvoker.cs b/src/Libraries/SmartStore.Core/Events/ConsumerInvoker.cs new file mode 100644 index 0000000000..9eb50ecb00 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/ConsumerInvoker.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Autofac; +using SmartStore.ComponentModel; +using SmartStore.Core.Async; +using SmartStore.Core.Logging; + +namespace SmartStore.Core.Events +{ + public class ConsumerInvoker : IConsumerInvoker + { + private readonly IConsumerResolver _resolver; + + public ConsumerInvoker(IConsumerResolver resolver) + { + _resolver = resolver; + + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void Invoke(ConsumerDescriptor descriptor, IConsumer consumer, ConsumeContext envelope) where TMessage : class + { + var d = descriptor; + var p = descriptor.WithEnvelope ? (object)envelope : envelope.Message; + var fastInvoker = FastInvoker.GetInvoker(d.Method); + + if (!d.FireForget && !d.IsAsync) + { + // The all synch case + try + { + InvokeCore(); + } + catch (Exception ex) + { + HandleException(ex, d); + } + } + else if (!d.FireForget && d.IsAsync) + { + //// The awaitable Task case + //BeginInvoke((Task)InvokeCore(cancelToken: AsyncRunner.AppShutdownCancellationToken), EndInvoke, d); + + // For now we must go with the AsyncRunner, the above call to BeginInvoke (APM > TPL) does not always + // guarantee that the task is awaited and throws exceptions especially when EF is involved. + using (var runner = AsyncRunner.Create()) + { + try + { + runner.Run((Task)InvokeCore(cancelToken: AsyncRunner.AppShutdownCancellationToken)); + } + catch (Exception ex) + { + HandleException(ex, d); + } + } + } + else if (d.FireForget && !d.IsAsync) + { + // A synch method should be executed async (without awaiting) + AsyncRunner.Run((c, ct, state) => InvokeCore(c, ct), d) + .ContinueWith(t => EndInvoke(t), /*TaskContinuationOptions.OnlyOnFaulted*/ TaskContinuationOptions.None) + .ConfigureAwait(false); + } + else if (d.FireForget && d.IsAsync) + { + // An async (Task) method should be executed without awaiting + AsyncRunner.Run((c, ct) => (Task)InvokeCore(c, ct), d) + .ContinueWith(t => EndInvoke(t), TaskContinuationOptions.OnlyOnFaulted) + .ConfigureAwait(false); + } + + object InvokeCore(IComponentContext c = null, CancellationToken cancelToken = default(CancellationToken)) + { + if (d.Parameters.Length == 0) + { + // Only one method param: the message! + return fastInvoker.Invoke(consumer, p); + } + + var parameters = new object[d.Parameters.Length + 1]; + parameters[0] = p; + + int i = 0; + foreach (var obj in ResolveParameters(c, d, cancelToken).ToArray()) + { + i++; + parameters[i] = obj; + } + + return fastInvoker.Invoke(consumer, parameters); + } + } + + private void HandleException(Exception ex, ConsumerDescriptor descriptor) + { + if (ex is AggregateException ae) + { + ae.InnerExceptions.Each(x => Logger.Error(x)); + } + else + { + Logger.Error(ex); + } + + if (!descriptor.FireForget) + { + throw ex; + } + } + + protected internal virtual IEnumerable ResolveParameters( + IComponentContext container, + ConsumerDescriptor descriptor, + CancellationToken cancelToken) + { + foreach (var p in descriptor.Parameters) + { + if (p.ParameterType == typeof(CancellationToken)) + { + yield return cancelToken; + } + else + { + yield return _resolver.ResolveParameter(p, container); + } + } + } + + #region APM > TPL pattern (does not always work stable) + + /// + /// Wraps a into the Begin method of an APM pattern. + /// + /// The task to wrap. + /// The callback method passed into the Begin method of the APM pattern. + /// The state passed into the Begin method of the APM pattern. + /// The asynchronous operation, to be returned by the Begin method of the APM pattern. + protected IAsyncResult BeginInvoke(Task task, AsyncCallback callback, ConsumerDescriptor descriptor) + { + var options = TaskCreationOptions.RunContinuationsAsynchronously; + var tcs = new TaskCompletionSource(descriptor, options); + + // "_ =" to discard 'async/await' compiler warning + _ = AwaitCompletionAsync(task, callback, tcs, descriptor); + + return tcs.Task; + } + + private async Task AwaitCompletionAsync( + Task task, + AsyncCallback callback, + TaskCompletionSource tcs, + ConsumerDescriptor descriptor) + { + try + { + await task; + tcs.TrySetResult(null); + } + catch (OperationCanceledException ex) + { + tcs.TrySetCanceled(ex.CancellationToken); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + finally + { + callback?.Invoke(tcs.Task); + } + } + + protected virtual void EndInvoke(IAsyncResult asyncResult) + { + var task = (Task)asyncResult; + + if (task.IsFaulted && task.Exception != null) + { + HandleException(task.Exception, (ConsumerDescriptor)asyncResult.AsyncState); + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs b/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs index 1ae4f063fb..0c233e9da1 100644 --- a/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs +++ b/src/Libraries/SmartStore.Core/Events/EventConsumerMetadata.cs @@ -5,8 +5,8 @@ namespace SmartStore.Core.Events { public class EventConsumerMetadata { - public bool ExecuteAsync { get; set; } public bool IsActive { get; set; } + public Type ContainerType { get; set; } public PluginDescriptor PluginDescriptor { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Events/FireForgetAttribute.cs b/src/Libraries/SmartStore.Core/Events/FireForgetAttribute.cs new file mode 100644 index 0000000000..a5076473c9 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/FireForgetAttribute.cs @@ -0,0 +1,20 @@ +using System; + +namespace SmartStore.Core.Events +{ + /// + /// Specifies whether an event consumer method should be invoked asynchronously without awaiting. + /// This can be advantegous in long running processes, because the current request thread will not 'block' + /// until method completion. + /// + /// + /// Use this with caution and only if you know what you are doing :-) + /// A class containing a fire & forget consumer should NOT take dependencies on request scoped services, because task continuation + /// happens on another thread and context gets lost. Pass required dependencies as method parameters instead, the consumer invoker + /// will spawn a new private context for the unit of work and resolve dependencies from this context. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class FireForgetAttribute : Attribute + { + } +} diff --git a/src/Libraries/SmartStore.Core/Events/IConsumer.cs b/src/Libraries/SmartStore.Core/Events/IConsumer.cs index 5fee38c56e..da2ff66d89 100644 --- a/src/Libraries/SmartStore.Core/Events/IConsumer.cs +++ b/src/Libraries/SmartStore.Core/Events/IConsumer.cs @@ -1,8 +1,45 @@ - +using System; + namespace SmartStore.Core.Events { - public interface IConsumer - { - void HandleEvent(T message); - } + /// + /// Marker interface for classes that contain one or more + /// event message handlers. A handler is a public instance method following some conventions. + /// The name of the handler method must be one of: + /// + /// void Handle(TMessage msg) + /// void HandleEvent(TMessage msg) + /// void Consume(TMessage msg) + /// Task HandleAsync(TMessage msg) + /// Task HandleEventAsync(TMessage msg) + /// Task ConsumeAsync(TMessage msg) + /// + /// Alternatively, the TMessage param can be the generic type. + /// + /// The decides how to call the method based on its signature: + /// + /// void method are invoked synchronously. + /// methods are invoked asynchronously and awaited. + /// The makes the method run in the background without awaiting. + /// + /// + /// + /// You can declare additional dependency parameters in the handler method, e.g.: + /// + /// Task HandleEventAsync(SomeEvent message, IDbContext dbContext, ICacheManager cache, CancellationToken cancelToken) + /// + /// Order does not matter. The invoker will auto-resolve appropriate instances and pass them to the method. But any unregistered + /// dependency or a primitive type will result in an exception (except of which always resolves to + /// ). + /// + /// + public interface IConsumer + { + } + + [Obsolete("Implement the non-generic 'IConsumer' interface instead.")] + public interface IConsumer : IConsumer + { + void HandleEvent(T message); + } } diff --git a/src/Libraries/SmartStore.Core/Events/IConsumerFactory.cs b/src/Libraries/SmartStore.Core/Events/IConsumerFactory.cs deleted file mode 100644 index a55c9fce79..0000000000 --- a/src/Libraries/SmartStore.Core/Events/IConsumerFactory.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace SmartStore.Core.Events -{ - - public interface IConsumerFactory - { - /// - /// Resolves all event consumers of type - /// - /// - /// Specifies whether only async consumers, sync consumers or both should be resolved - /// (null = all, true = only async, false = only sync) - /// - /// The consumer instances - IEnumerable> GetConsumers(bool? resolveAsyncs = null); - - bool HasAsyncConsumer { get; } - } - -} diff --git a/src/Libraries/SmartStore.Core/Events/IConsumerInvoker.cs b/src/Libraries/SmartStore.Core/Events/IConsumerInvoker.cs new file mode 100644 index 0000000000..85036e7528 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/IConsumerInvoker.cs @@ -0,0 +1,12 @@ +using System; + +namespace SmartStore.Core.Events +{ + /// + /// Responsible for invoking event message handler methods. + /// + public interface IConsumerInvoker + { + void Invoke(ConsumerDescriptor descriptor, IConsumer consumer, ConsumeContext envelope) where TMessage : class; + } +} diff --git a/src/Libraries/SmartStore.Core/Events/IConsumerRegistry.cs b/src/Libraries/SmartStore.Core/Events/IConsumerRegistry.cs new file mode 100644 index 0000000000..4339d6fb4f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/IConsumerRegistry.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Events +{ + /// + /// A registry for fast lookup. + /// + public interface IConsumerRegistry + { + IEnumerable GetConsumers(object message); + } +} diff --git a/src/Libraries/SmartStore.Core/Events/IConsumerResolver.cs b/src/Libraries/SmartStore.Core/Events/IConsumerResolver.cs new file mode 100644 index 0000000000..7a7be293d7 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Events/IConsumerResolver.cs @@ -0,0 +1,16 @@ +using System; +using System.Reflection; +using Autofac; + +namespace SmartStore.Core.Events +{ + /// + /// Resolves the concrete class instance which contains + /// the requested consumer. + /// + public interface IConsumerResolver + { + IConsumer Resolve(ConsumerDescriptor descriptor); + object ResolveParameter(ParameterInfo p, IComponentContext c = null); + } +} diff --git a/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs b/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs index 54fb88e357..21f0f6ebe6 100644 --- a/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs +++ b/src/Libraries/SmartStore.Core/Events/IEventPublisher.cs @@ -1,8 +1,9 @@ - +using System; + namespace SmartStore.Core.Events { - public interface IEventPublisher - { - void Publish(T eventMessage); - } + public interface IEventPublisher + { + void Publish(T message) where T : class; + } } diff --git a/src/Libraries/SmartStore.Core/Events/NullEventPublisher.cs b/src/Libraries/SmartStore.Core/Events/NullEventPublisher.cs index d0bff29ca8..07d7ae6e11 100644 --- a/src/Libraries/SmartStore.Core/Events/NullEventPublisher.cs +++ b/src/Libraries/SmartStore.Core/Events/NullEventPublisher.cs @@ -10,7 +10,7 @@ public static IEventPublisher Instance get { return s_instance; } } - public void Publish(T eventMessage) + public void Publish(T eventMessage) where T : class { // Noop } diff --git a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs index b6fec3780a..5bd474e782 100644 --- a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Routing; +using SmartStore.Collections; namespace SmartStore { @@ -14,20 +11,36 @@ public static void AddRange(this ICollection initial, IEnumerable other if (other == null) return; - var list = initial as List; + if (initial is List list) + { + list.AddRange(other); + return; + } - if (list != null) - { - list.AddRange(other); - return; - } - - other.Each(x => initial.Add(x)); + foreach (var local in other) + { + initial.Add(local); + } } - public static bool IsNullOrEmpty(this ICollection source) + public static SyncedCollection AsSynchronized(this ICollection source) + { + return AsSynchronized(source, new object()); + } + + public static SyncedCollection AsSynchronized(this ICollection source, object syncRoot) + { + if (source is SyncedCollection sc) + { + return sc; + } + + return new SyncedCollection(source, syncRoot); + } + + public static bool IsNullOrEmpty(this ICollection source) { - return (source == null || source.Count == 0); + return source == null || source.Count == 0; } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs index d95b37af6d..58992963be 100644 --- a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs @@ -45,7 +45,8 @@ public static object Convert(this object value, Type to) public static object Convert(this object value, Type to, CultureInfo culture) { - Guard.NotNull(to, nameof(to)); + if (to == null) + throw new ArgumentNullException(nameof(to)); if (value == null || value == DBNull.Value || to.IsInstanceOfType(value)) { @@ -92,6 +93,7 @@ public static char ToHex(this int value) { return (char)(value + 48); } + return (char)((value - 10) + 97); } @@ -101,8 +103,7 @@ public static char ToHex(this int value) public static int ToInt(this string value, int defaultValue = 0) { - int result; - if (CommonHelper.TryConvert(value, out result)) + if (CommonHelper.TryConvert(value, out int result)) { return result; } @@ -112,18 +113,17 @@ public static int ToInt(this string value, int defaultValue = 0) public static char ToChar(this string value, bool unescape = false, char defaultValue = '\0') { - char result; - if (value.HasValue() && char.TryParse(unescape ? Regex.Unescape(value) : value, out result)) + if (value.HasValue() && char.TryParse(unescape ? Regex.Unescape(value) : value, out char result)) { return result; } + return defaultValue; } public static float ToFloat(this string value, float defaultValue = 0) { - float result; - if (CommonHelper.TryConvert(value, out result)) + if (CommonHelper.TryConvert(value, out float result)) { return result; } @@ -133,8 +133,7 @@ public static float ToFloat(this string value, float defaultValue = 0) public static bool ToBool(this string value, bool defaultValue = false) { - bool result; - if (CommonHelper.TryConvert(value, out result)) + if (CommonHelper.TryConvert(value, out bool result)) { return result; } @@ -182,13 +181,13 @@ public static bool ToBool(this string value, bool defaultValue = false) { if (value.HasValue()) { - DateTime dt; - if (DateTime.TryParseExact(value, "o", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt)) + if (DateTime.TryParseExact(value, "o", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out DateTime dt)) return dt; if (DateTime.TryParseExact(value, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out dt)) return dt; } + return null; } @@ -211,29 +210,41 @@ public static Version ToVersion(this string value, Version defaultVersion = null public static byte[] ToByteArray(this Stream stream) { - Guard.NotNull(stream, nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - if (stream is MemoryStream) + if (stream is MemoryStream mem) { - return ((MemoryStream)stream).ToArray(); + if (mem.TryGetBuffer(out var buffer)) + { + return buffer.Array; + } + + return mem.ToArray(); } else { using (var streamReader = new MemoryStream()) { stream.CopyTo(streamReader); - return streamReader.ToArray(); + return streamReader.GetBuffer(); } } } public static async Task ToByteArrayAsync(this Stream stream) { - Guard.NotNull(stream, nameof(stream)); + if (stream == null) + throw new ArgumentNullException(nameof(stream)); - if (stream is MemoryStream) + if (stream is MemoryStream mem) { - return ((MemoryStream)stream).ToArray(); + if (mem.TryGetBuffer(out var buffer)) + { + return buffer.Array; + } + + return mem.ToArray(); } else { @@ -257,7 +268,8 @@ public static Task AsStringAsync(this Stream stream) public static string AsString(this Stream stream, Encoding encoding) { - Guard.NotNull(encoding, nameof(encoding)); + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); // convert stream to string string result; @@ -277,7 +289,8 @@ public static string AsString(this Stream stream, Encoding encoding) public static Task AsStringAsync(this Stream stream, Encoding encoding) { - Guard.NotNull(encoding, nameof(encoding)); + if (encoding == null) + throw new ArgumentNullException(nameof(encoding)); // convert stream to string Task result; @@ -328,7 +341,8 @@ public static Stream ToStream(this byte[] bytes) [DebuggerStepThrough] public static string Hash(this byte[] value, bool toBase64 = false) { - Guard.NotNull(value, nameof(value)); + if (value == null) + throw new ArgumentNullException(nameof(value)); using (MD5 md5 = MD5.Create()) { @@ -360,7 +374,8 @@ public static string Hash(this byte[] value, bool toBase64 = false) /// The compressed result public static byte[] Zip(this byte[] buffer) { - Guard.NotNull(buffer, nameof(buffer)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); using (var compressedStream = new MemoryStream()) using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress)) @@ -378,7 +393,8 @@ public static byte[] Zip(this byte[] buffer) /// The decompressed result public static byte[] UnZip(this byte[] buffer) { - Guard.NotNull(buffer, nameof(buffer)); + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); using (var compressedStream = new MemoryStream(buffer)) using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress)) diff --git a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs index 3070172cdc..072e55229d 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,17 @@ 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 +63,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 +85,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 +110,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 +123,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/DictionaryExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs index b490bb012d..3a3edf2f83 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs @@ -48,28 +48,26 @@ public static void Merge(this IDictionary instance, } } - public static void AppendInValue(this IDictionary instance, string key, string separator, object value) + public static void AppendInValue(this IDictionary instance, string key, string separator, string value) { AddInValue(instance, key, separator, value, false); } - public static void PrependInValue(this IDictionary instance, string key, string separator, object value) + public static void PrependInValue(this IDictionary instance, string key, string separator, string value) { AddInValue(instance, key, separator, value, true); } - private static void AddInValue(IDictionary instance, string key, string separator, object value, bool prepend = false) + private static void AddInValue(IDictionary instance, string key, string separator, string value, bool prepend = false) { - var valueStr = value.ToString(); - - if (!instance.ContainsKey(key)) + if (!instance.TryGetValue(key, out var obj)) { - instance[key] = valueStr; + instance[key] = value; } else { - var arr = instance[key].ToString().Trim().Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); - var arrValue = valueStr.Trim().Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); + var arr = obj.ToString().Trim().Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); + var arrValue = value.Trim().Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); arr = prepend ? arrValue.Union(arr) : arr.Union(arrValue); @@ -79,7 +77,8 @@ private static void AddInValue(IDictionary instance, string key, public static TValue Get(this IDictionary instance, TKey key) { - Guard.NotNull(instance, nameof(instance)); + if (instance == null) + throw new ArgumentNullException(nameof(instance)); instance.TryGetValue(key, out var val); return val; @@ -87,7 +86,8 @@ public static TValue Get(this IDictionary instance, public static ExpandoObject ToExpandoObject(this IDictionary source, bool castIfPossible = false) { - Guard.NotNull(source, nameof(source)); + if (source == null) + throw new ArgumentNullException(nameof(source)); if (castIfPossible && source is ExpandoObject) { @@ -100,6 +100,43 @@ public static ExpandoObject ToExpandoObject(this IDictionary sou return result; } + + public static bool TryAdd(this Dictionary source, TKey key, TValue value, bool updateIfExists = false) + { + if (source == null || key == null) + { + return false; + } + + if (source.ContainsKey(key)) + { + if (updateIfExists) + { + source[key] = value; + return true; + } + } + else + { + source.Add(key, value); + return true; + } + + return false; + } + + public static bool TryRemove(this Dictionary source, TKey key, out TValue value) + { + value = default(TValue); + + if (source != null && key != null && source.TryGetValue(key, out value)) + { + source.Remove(key); + return true; + } + + return false; + } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index 87abdd2e99..cb529dcace 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -241,9 +241,14 @@ public static Dictionary ToDictionarySafe elementSelector, IEqualityComparer comparer) { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(keySelector, nameof(keySelector)); - Guard.NotNull(elementSelector, nameof(elementSelector)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + if (elementSelector == null) + throw new ArgumentNullException(nameof(elementSelector)); var dictionary = new Dictionary(comparer); @@ -276,8 +281,11 @@ public static IEnumerable DistinctBy(this IEnumerableThe sorted entity collection public static IEnumerable OrderBySequence(this IEnumerable source, IEnumerable ids) where TEntity : BaseEntity { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(ids, nameof(ids)); + if (source == null) + throw new ArgumentNullException(nameof(source)); + + if (ids == null) + throw new ArgumentNullException(nameof(ids)); var sorted = from id in ids join entity in source on id equals entity.Id @@ -286,6 +294,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,29 +308,44 @@ 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) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); - foreach (var item in source) - { - map.Add(keySelector(item), valueSelector(item)); - } + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); - return map; - } + if (valueSelector == null) + throw new ArgumentNullException(nameof(valueSelector)); - #endregion + var map = new Multimap(comparer); + + foreach (var item in source) + { + map.Add(keySelector(item), valueSelector(item)); + } + + return map; + } - #region NameValueCollection + #endregion + + #region NameValueCollection - public static void AddRange(this NameValueCollection initial, NameValueCollection other) + public static void AddRange(this NameValueCollection initial, NameValueCollection other) { - Guard.NotNull(initial, "initial"); + if (initial == null) + throw new ArgumentNullException(nameof(initial)); - if (other == null) + if (other == null) return; foreach (var item in other.AllKeys) @@ -366,5 +394,65 @@ public static string BuildQueryString(this NameValueCollection nvc, Encoding enc } #endregion - } + + #region List + + /// + /// Safe way to remove selected entries from a list. + /// + /// To be used for materialized lists only, not IEnumerable or similar. + /// Object type. + /// List. + /// Selector for the entries to be removed. + /// Number of removed entries. + public static int Remove(this IList list, Func selector) + { + Guard.NotNull(list, nameof(list)); + Guard.NotNull(selector, nameof(selector)); + + var count = 0; + for (var i = list.Count - 1; i >= 0; i--) + { + if (selector(list[i])) + { + list.RemoveAt(i); + ++count; + } + } + + return count; + } + + #endregion + + #region Stack + + public static bool TryPeek(this Stack stack, out T value) + { + value = default(T); + + if (stack.Count > 0) + { + value = stack.Peek(); + return true; + } + + return false; + } + + public static bool TryPop(this Stack stack, out T value) + { + value = default(T); + + if (stack.Count > 0) + { + value = stack.Pop(); + return true; + } + + return false; + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs index 7810a19b12..59851619db 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HttpExtensions.cs @@ -11,27 +11,64 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core; using System.Web.Mvc; +using SmartStore.Core.Fakes; namespace SmartStore -{ - public static class HttpExtensions - { - private const string HTTP_CLUSTER_VAR = "HTTP_CLUSTER_HTTPS"; - private const string HTTP_XFWDPROTO_VAR = "HTTP_X_FORWARDED_PROTO"; +{ + public static class HttpExtensions + { + private const string CACHE_REGION_NAME = "SmartStoreNET:"; + + private static readonly List> _sslHeaders = new List> + { + new Tuple("HTTP_CLUSTER_HTTPS", "on"), + new Tuple("X-Forwarded-Proto", "https"), + new Tuple("x-arr-ssl", null), + new Tuple("X-Forwarded-Protocol", "https"), + new Tuple("X-Forwarded-Ssl", "on"), + new Tuple("X-Url-Scheme", "https") + }; /// - /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). - /// Works with Cloud's load balancers. + /// Tries to get the instance without throwing exceptions /// - /// - /// - public static bool IsSecureConnection(this HttpRequestBase request) - { - return (request.IsSecureConnection - || (request.ServerVariables[HTTP_CLUSTER_VAR] != null || request.ServerVariables[HTTP_CLUSTER_VAR] == "on") - || (request.ServerVariables[HTTP_XFWDPROTO_VAR] != null || request.ServerVariables[HTTP_XFWDPROTO_VAR] == "https")); - } + /// The instance or null. + public static HttpRequestBase SafeGetHttpRequest(this HttpContext httpContext) + { + if (httpContext == null) + { + return null; + } + + return SafeGetHttpRequest(new HttpContextWrapper(httpContext)); + } + + /// + /// Tries to get the instance without throwing exceptions + /// + /// The instance or null. + public static HttpRequestBase SafeGetHttpRequest(this HttpContextBase httpContext) + { + if (httpContext == null) + { + return null; + } + + if (httpContext.Handler != null || httpContext is FakeHttpContext) + { + // Having a handler means we're most likely in the MVC routing pipeline. + return httpContext.Request; + } + try + { + return httpContext.Request; + } + catch + { + return null; + } + } /// /// Returns wether the specified url is local to the host or not @@ -41,7 +78,6 @@ public static bool IsSecureConnection(this HttpRequestBase request) /// public static bool IsAppLocalUrl(this HttpRequestBase request, string url) { - if (string.IsNullOrWhiteSpace(url)) { return false; @@ -103,10 +139,35 @@ public static bool IsAppLocalUrl(this HttpRequestBase request, string url) /// public static bool IsSecureConnection(this HttpRequest request) { - return (request.IsSecureConnection || (request.ServerVariables[HTTP_CLUSTER_VAR] != null || request.ServerVariables[HTTP_CLUSTER_VAR] == "on")); + return IsSecureConnection(new HttpRequestWrapper(request)); } - [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + /// + /// Gets a value which indicates whether the HTTP connection uses secure sockets (HTTPS protocol). + /// Works with Cloud's load balancers. + /// + /// + /// + public static bool IsSecureConnection(this HttpRequestBase request) + { + if (request.IsSecureConnection) + { + return true; + } + + foreach (var tuple in _sslHeaders) + { + var serverVar = request.ServerVariables[tuple.Item1]; + if (serverVar != null) + { + return tuple.Item2 == null || tuple.Item2.Equals(serverVar, StringComparison.OrdinalIgnoreCase); + } + } + + return false; + } + + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] public static void SetFormsAuthenticationCookie(this HttpWebRequest webRequest, HttpRequestBase httpRequest) { CopyCookie(webRequest, httpRequest, FormsAuthentication.FormsCookieName); @@ -140,7 +201,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 +231,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 +264,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 GetRootControllerContext(this ControllerContext controllerContext) { Guard.NotNull(controllerContext, nameof(controllerContext)); @@ -236,6 +297,21 @@ public static ControllerContext GetMasterControllerContext(this ControllerContex return ctx; } - } + public static bool IsBareBonePage(this ControllerContext controllerContext) + { + var ctx = controllerContext.GetRootControllerContext(); + + if (ctx is ViewContext viewContext) + { + // IsPopUp or Framed + if (viewContext.ViewBag.IsPopup == true || viewContext.ViewBag.Framed == true) + { + return true; + } + } + + return false; + } + } } diff --git a/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs index d176d04adb..2104c9c3ee 100644 --- a/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/IOExtensions.cs @@ -1,15 +1,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace SmartStore { public static class IOExtensions - { - + { public static bool IsFileLocked(this FileInfo file) { if (file == null) @@ -23,10 +19,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 +35,4 @@ public static bool IsFileLocked(this FileInfo file) return false; } } - } diff --git a/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs index a86de51898..ebb5faccf2 100644 --- a/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/LinqExtensions.cs @@ -22,31 +22,31 @@ public static FieldInfo ExtractFieldInfo(this LambdaExpression propertyAccessor) [SuppressMessage("ReSharper", "CanBeReplacedWithTryCastAndCheckForNull")] public static MemberInfo ExtractMemberInfo(this LambdaExpression propertyAccessor) { - Guard.NotNull(propertyAccessor, nameof(propertyAccessor)); + if (propertyAccessor == null) + throw new ArgumentNullException(nameof(propertyAccessor)); - MemberInfo info; + MemberInfo info; try { MemberExpression operand; // o => o.PropertyOrField LambdaExpression expression = propertyAccessor; - - if (expression.Body is UnaryExpression) - { - // If the property is not an Object, then the member access expression will be wrapped in a conversion expression - // (object)o.PropertyOrField - UnaryExpression body = (UnaryExpression)expression.Body; - // o.PropertyOrField - operand = (MemberExpression)body.Operand; - } - else - { - // o.PropertyOrField - operand = (MemberExpression)expression.Body; - } - - // Member - MemberInfo member = operand.Member; + // If the property is not an Object, then the member access expression will be wrapped in a conversion expression + // (object)o.PropertyOrField + + if (expression.Body is UnaryExpression body) + { + // o.PropertyOrField + operand = (MemberExpression)body.Operand; + } + else + { + // o.PropertyOrField + operand = (MemberExpression)expression.Body; + } + + // Member + MemberInfo member = operand.Member; info = member; } catch (Exception e) diff --git a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs index f4452fb5ed..3241c7d8fa 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -1,14 +1,9 @@ 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 -{ +{ public static class MiscExtensions { public static void Dump(this Exception exception) @@ -21,7 +16,7 @@ public static void Dump(this Exception exception) catch { } } - public static string ToAllMessages(this Exception exception) + public static string ToAllMessages(this Exception exception, bool includeStackTrace = false) { var sb = new StringBuilder(); @@ -29,10 +24,24 @@ public static string ToAllMessages(this Exception exception) { if (!sb.ToString().EmptyNull().Contains(exception.Message)) { - sb.Grow(exception.Message, " * "); + if (includeStackTrace) + { + if (sb.Length > 0) + { + sb.AppendLine(); + sb.AppendLine(); + } + sb.AppendLine(exception.ToString()); + } + else + { + sb.Grow(exception.Message, " * "); + } } + exception = exception.InnerException; } + return sb.ToString(); } @@ -51,11 +60,15 @@ 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) - return ""; + if (bytes == null || bytes.Length <= 0) + { + return ""; + } var sb = new StringBuilder(); @@ -63,9 +76,12 @@ public static string ToHexString(this byte[] bytes, int length = 0) { sb.Append(b.ToString("x2")); - if (length > 0 && sb.Length >= length) - break; + if (length > 0 && sb.Length >= length) + { + break; + } } + return sb.ToString(); } @@ -77,20 +93,24 @@ public static string ToHexString(this byte[] bytes, int length = 0) /// Delimiter to use public static void Grow(this StringBuilder sb, string grow, string delimiter) { - Guard.NotNull(delimiter, "delimiter"); + Guard.NotNull(delimiter, nameof(delimiter)); if (!string.IsNullOrWhiteSpace(grow)) { - if (sb.Length <= 0) - sb.Append(grow); - else - sb.AppendFormat("{0}{1}", delimiter, grow); + if (sb.Length <= 0) + { + sb.Append(grow); + } + else + { + sb.AppendFormat("{0}{1}", delimiter, grow); + } } } public static string SafeGet(this string[] arr, int index) { - return (arr != null && index < arr.Length ? arr[index] : ""); + return arr != null && index < arr.Length ? arr[index] : ""; } } } diff --git a/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs new file mode 100644 index 0000000000..e85c5c9a5f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Extensions/NumericExtensions.cs @@ -0,0 +1,160 @@ +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) + { + if (currency == null) + throw new ArgumentNullException(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..845d7a9a79 100644 --- a/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/RouteExtensions.cs @@ -9,11 +9,11 @@ public static class RouteExtensions { public static string GetAreaName(this RouteData routeData) { - object area; - if (routeData.DataTokens.TryGetValue("area", out area)) + if (routeData.DataTokens.TryGetValue("area", out object area)) { return (area as string); } + return routeData.Route.GetAreaName(); } @@ -24,18 +24,20 @@ 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; } - /// - /// Generates an identifier for the given route in the form "[{area}.]{controller}.{action}" - /// - public static string GenerateRouteIdentifier(this RouteData routeData) + /// + /// Generates an identifier for the given route in the form "[{area}.]{controller}.{action}" + /// + public static string GenerateRouteIdentifier(this RouteData routeData) { string area = routeData.GetAreaName(); string controller = routeData.GetRequiredString("controller"); diff --git a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs index 5a93a85543..6b286561a0 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StreamExtensions.cs @@ -59,22 +59,26 @@ public static bool ToFile(this Stream srcStream, string path) public static bool ContentsEqual(this Stream src, Stream other) { - Guard.NotNull(src, nameof(src)); - Guard.NotNull(other, nameof(other)); + if (src == null) + throw new ArgumentNullException(nameof(src)); - if (src.Length != other.Length) + if (other == null) + throw new ArgumentNullException(nameof(other)); + + if (src.Length != other.Length) return false; - const int bufferSize = 2048; - byte[] buffer1 = new byte[bufferSize]; - byte[] buffer2 = new byte[bufferSize]; + const int intSize = sizeof(Int64); + const int bufferSize = 2048; + var buffer1 = new byte[bufferSize]; + var buffer2 = new byte[bufferSize]; while (true) { int len1 = src.Read(buffer1, 0, bufferSize); int len2 = other.Read(buffer2, 0, bufferSize); - if (len1 != len2) + if (len1 != len2) return false; if (len1 == 0) @@ -83,7 +87,7 @@ public static bool ContentsEqual(this Stream src, Stream other) int iterations = (int)Math.Ceiling((double)len1 / sizeof(Int64)); for (int i = 0; i < iterations; i++) { - if (BitConverter.ToInt64(buffer1, i * sizeof(Int64)) != BitConverter.ToInt64(buffer2, i * sizeof(Int64))) + if (BitConverter.ToInt64(buffer1, i * intSize) != BitConverter.ToInt64(buffer2, i * intSize)) { return false; } diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index f435df42ac..211fb926af 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -11,6 +11,7 @@ using System.Linq; using AngleSharp.Dom; using AngleSharp.Parser.Html; +using SmartStore.Core.Html; namespace SmartStore { @@ -22,32 +23,33 @@ public static class StringExtensions public const char LineFeed = '\n'; public const char Tab = '\t'; - private delegate void ActionLine(TextWriter textWriter, string line); + private delegate void ActionLine(TextWriter textWriter, string line); #region Char extensions [DebuggerStepThrough] public static int ToInt(this char value) { - if ((value >= '0') && (value <= '9')) + if (value >= '0' && value <= '9') { - return (value - '0'); + return value - '0'; } - if ((value >= 'a') && (value <= 'f')) + else if (value >= 'a' && value <= 'f') { - return ((value - 'a') + 10); + return (value - 'a') + 10; } - if ((value >= 'A') && (value <= 'F')) + else if (value >= 'A' && value <= 'F') { - return ((value - 'A') + 10); + return (value - 'A') + 10; } + return -1; } [DebuggerStepThrough] public static string ToUnicode(this char c) { - using (StringWriter w = new StringWriter(CultureInfo.InvariantCulture)) + using (var w = new StringWriter(CultureInfo.InvariantCulture)) { WriteCharAsUnicode(c, w); return w.ToString(); @@ -71,11 +73,22 @@ internal static void WriteCharAsUnicode(char c, TextWriter writer) writer.Write(h4); } - #endregion + public static char TryRemoveDiacritic(this char c) + { + var normalized = c.ToString().Normalize(NormalizationForm.FormD); + if (normalized.Length > 1) + { + return normalized[0]; + } - #region String extensions + return c; + } - [DebuggerStepThrough] + #endregion + + #region String extensions + + [DebuggerStepThrough] public static T ToEnum(this string value, T defaultValue) { if (!value.HasValue()) @@ -99,7 +112,8 @@ public static string ToSafe(this string value, string defaultValue = null) { return value; } - return (defaultValue ?? String.Empty); + + return (defaultValue ?? string.Empty); } [DebuggerStepThrough] @@ -209,9 +223,10 @@ public static bool IsEmpty(this string value) [DebuggerStepThrough] public static bool IsWhiteSpace(this string value) { - Guard.NotNull(value, "value"); + if (value == null) + throw new ArgumentNullException(nameof(value)); - if (value.Length == 0) + if (value.Length == 0) return false; for (int i = 0; i < value.Length; i++) @@ -263,10 +278,11 @@ public static string Mask(this string value, int length) { if (value.HasValue()) return value.Substring(0, length) + new String('*', value.Length - length); + return value; } - private static bool IsWebUrlInternal(this string value, bool schemeIsOptional) + private static bool IsWebUrlInternal(string value, bool schemeIsOptional) { if (String.IsNullOrEmpty(value)) return false; @@ -280,29 +296,18 @@ private static bool IsWebUrlInternal(this string value, bool schemeIsOptional) return Uri.IsWellFormedUriString(value, UriKind.Absolute) && (value.StartsWith("http://") || value.StartsWith("https://") || value.StartsWith("ftp://")); - - #region Old (obsolete) - //// Uri.TryCreate() does not accept port numbers in uri strings. - //if (schemeIsOptional) - //{ - // Uri uri; - // return Uri.TryCreate(value, UriKind.Absolute, out uri); - //} - - //return RegularExpressions.IsWebUrl.IsMatch(value.Trim()); - #endregion } [DebuggerStepThrough] public static bool IsWebUrl(this string value) { - return value.IsWebUrlInternal(false); + return IsWebUrlInternal(value, false); } [DebuggerStepThrough] public static bool IsWebUrl(this string value, bool schemeIsOptional) { - return value.IsWebUrlInternal(schemeIsOptional); + return IsWebUrlInternal(value, schemeIsOptional); } [DebuggerStepThrough] @@ -352,17 +357,19 @@ public static bool IsAlphaNumeric(this string value) [DebuggerStepThrough] public static string Truncate(this string value, int maxLength, string suffix = "") { - Guard.NotNull(suffix, "suffix"); + if (suffix == null) + throw new ArgumentNullException(nameof(suffix)); + Guard.IsPositive(maxLength, nameof(maxLength)); - int subStringLength = maxLength - suffix.Length; + int subStringLength = maxLength - suffix.Length; if (subStringLength <= 0) - throw Error.Argument("maxLength", "Length of suffix string is greater or equal to maximumLength"); + throw Error.Argument(nameof(maxLength), "Length of suffix string is greater or equal to maximumLength"); if (value != null && value.Length > maxLength) { - string truncatedString = value.Substring(0, subStringLength); + var truncatedString = value.Substring(0, subStringLength); // in case the last character is a space truncatedString = truncatedString.Trim(); truncatedString += suffix; @@ -473,92 +480,6 @@ public static IEnumerable GetLines(this string input, bool trimLines = f } } - ///// - ///// Removes all redundant whitespace (empty lines, double space etc.). - ///// Use ~! literal to keep whitespace wherever necessary. - ///// - ///// Input - ///// The compacted string - //public static string Compact(this string input) - //{ - // Guard.NotNull(input, nameof(input)); - - // var isNewLine = false; - // var isBlank = false; - // var isChar = false; - // var isLiteral = false; // When we detect the ~! literal - // var len = input.Length; - // int i = 0; - // var eof = false; - - // var sb = new StringBuilder(); - - // for (i = 0; i < len; i++) - // { - // var c = input[i]; - - // eof = i == len - 1; - - // if (Char.IsWhiteSpace(c)) - // { - // if (c == '\r' && !eof && input[i + 1] == '\n') - // { - // // \r\n detected, don't double-check - // continue; - // } - - // if (c == '\r' || c == '\n') - // { - // // New line - // if (i > 0 && sb[sb.Length - 1] == ' ') - // { - // // If NewLine is detected, trim end (all trailing whitespace) - // sb.Remove(sb.Length - 1, 1); - // } - // } - // else - // { - // // Space, tab etc. - // if (isChar) - // { - // // If last char not empty, append the space... - // sb.Append(' '); - // } - // } - - // isLiteral = false; - // isChar = false; - // isBlank = true; - // isNewLine = c == '\r' || c == '\n'; - // } - // else // No WhiteSpace - // { - // if (isNewLine) - // { - // // First non-blank char in current line: write NewLine first. - // sb.AppendLine(); - // } - - // isLiteral = c == '~' && !eof && input[i + 1] == '!'; - // isChar = true; - // isNewLine = false; - // isBlank = false; - - // if (isLiteral) - // { - // sb.Append(' '); - // i++; // skip next "!" char - // } - // else - // { - // sb.Append(c); - // } - // } - // } - - // return sb.ToString(); - //} - /// /// Ensure that a string starts with a string. /// @@ -568,8 +489,11 @@ public static IEnumerable GetLines(this string input, bool trimLines = f [DebuggerStepThrough] public static string EnsureStartsWith(this string value, string startsWith) { - Guard.NotNull(value, "value"); - Guard.NotNull(startsWith, "startsWith"); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (startsWith == null) + throw new ArgumentNullException(nameof(startsWith)); return value.StartsWith(startsWith) ? value : (startsWith + value); } @@ -583,10 +507,13 @@ public static string EnsureStartsWith(this string value, string startsWith) [DebuggerStepThrough] public static string EnsureEndsWith(this string value, string endWith) { - Guard.NotNull(value, "value"); - Guard.NotNull(endWith, "endWith"); + if (value == null) + throw new ArgumentNullException(nameof(value)); - if (value.Length >= endWith.Length) + if (endWith == null) + throw new ArgumentNullException(nameof(endWith)); + + if (value.Length >= endWith.Length) { if (string.Compare(value, value.Length - endWith.Length, endWith, 0, endWith.Length, StringComparison.OrdinalIgnoreCase) == 0) return value; @@ -638,34 +565,7 @@ public static string RemoveHtml(this string source, ICollection removeTa public static string RemoveHtml(this string source) { - if (source.IsEmpty()) - return string.Empty; - - var ignoreTags = new HashSet(StringComparer.OrdinalIgnoreCase) { "script", "style", "svg", "img" }; - - var parser = new HtmlParser(); - var doc = parser.Parse(source); - - var treeWalker = doc.CreateTreeWalker(doc.Body, FilterSettings.Text); - - var sb = new StringBuilder(); - - var node = treeWalker.ToNext(); - while (node != null) - { - if (!ignoreTags.Contains(node.Parent.NodeName)) - { - var text = node.TextContent; - if (text.HasValue()) - { - sb.AppendLine(text); - } - } - - node = treeWalker.ToNext(); - } - - return sb.ToString().HtmlDecode(); + return HtmlUtils.StripTags(source).Trim().HtmlDecode(); } /// @@ -677,10 +577,10 @@ public static string RemoveHtml(this string source) [DebuggerStepThrough] public static string SplitPascalCase(this string value) { - //return Regex.Replace(input, "([A-Z][a-z])", " $1", RegexOptions.Compiled).Trim(); - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); char[] ca = value.ToCharArray(); sb.Append(ca[0]); + for (int i = 1; i < ca.Length - 1; i++) { char c = ca[i]; @@ -690,6 +590,7 @@ public static string SplitPascalCase(this string value) } sb.Append(c); } + if (ca.Length > 1) { sb.Append(ca[ca.Length - 1]); @@ -710,48 +611,56 @@ public static string[] SplitSafe(this string value, string separator) if (string.IsNullOrEmpty(value)) return new string[0]; - // do not use separator.IsEmpty() here because whitespace like " " is a valid separator. + // Do not use separator.IsEmpty() here because whitespace like " " is a valid separator. // an empty separator "" returns array with value. if (separator == null) { - separator = "|"; - - if (value.IndexOf(separator) < 0) + for (var i = 0; i < value.Length; i++) { - if (value.IndexOf(';') > -1) + var c = value[i]; + if (c == ';' || c == ',' || c == '|') { - separator = ";"; + return value.Split(new char[] { c }, StringSplitOptions.RemoveEmptyEntries); } - else if (value.IndexOf(',') > -1) + if (c == '\r' && (i + 1) < value.Length & value[i + 1] == '\n') { - separator = ","; - } - else if (value.IndexOf(Environment.NewLine) > -1) - { - separator = Environment.NewLine; + return value.GetLines(false, true).ToArray(); } } - } - return value.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + return new string[] { value }; + } + else + { + return value.Split(new string[] { separator }, StringSplitOptions.RemoveEmptyEntries); + } } /// Splits a string into two strings /// 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; } @@ -765,8 +674,8 @@ public static string EncodeJsString(this string value) [DebuggerStepThrough] public static string EncodeJsString(this string value, char delimiter, bool appendDelimiters) { - StringBuilder sb = new StringBuilder(value != null ? value.Length : 16); - using (StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture)) + var sb = new StringBuilder(value != null ? value.Length : 16); + using (var w = new StringWriter(sb, CultureInfo.InvariantCulture)) { EncodeJsString(w, value, delimiter, appendDelimiters); return w.ToString(); @@ -883,7 +792,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,21 +804,25 @@ 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. + /// + /// Returns n/a if string is empty else self. + /// [DebuggerStepThrough] public static string NaIfEmpty(this string value) { - return (value.HasValue() ? value : "n/a"); + return (string.IsNullOrWhiteSpace(value) ? "n/a" : value); } - /// Replaces substring with position x1 to x2 by replaceBy. + /// + /// Replaces substring with position x1 to x2 by replaceBy. + /// [DebuggerStepThrough] public static string Replace(this string value, int x1, int x2, string replaceBy = null) { - if (value.HasValue() && x1 > 0 && x2 > x1 && x2 < value.Length) + if (!string.IsNullOrWhiteSpace(value) && x1 > 0 && x2 > x1 && x2 < value.Length) { return value.Substring(0, x1) + (replaceBy.EmptyNull()) + value.Substring(x2 + 1); } @@ -936,9 +851,34 @@ public static string Replace(this string value, string oldValue, string newValue { exc.Dump(); } + 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) + { + if (value == null) + throw new ArgumentNullException(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) { @@ -946,14 +886,15 @@ public static string TrimSafe(this string value) } [DebuggerStepThrough] - public static string Prettify(this string value, bool allowSpace = false, char[] allowChars = null) + public static string Slugify(this string value, bool allowSpace = false, char[] allowChars = null) { - string res = ""; + string res = string.Empty; + try { - if (value.HasValue()) + if (!string.IsNullOrWhiteSpace(value)) { - StringBuilder sb = new StringBuilder(); + var sb = new StringBuilder(); bool space = false; char ch; @@ -973,7 +914,8 @@ public static string Prettify(this string value, bool allowSpace = false, char[] space = false; - if ((ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122)) { + if ((ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122) || ch == '_') + { sb.Append(ch); continue; } @@ -984,46 +926,43 @@ public static string Prettify(this string value, bool allowSpace = false, char[] continue; } - switch (ch) { - case '_': sb.Append(ch); break; - - case 'ä': sb.Append("ae"); break; - case 'ö': sb.Append("oe"); break; - case 'ü': sb.Append("ue"); break; - case 'ß': sb.Append("ss"); break; - case 'Ä': sb.Append("AE"); break; - case 'Ö': sb.Append("OE"); break; - case 'Ü': sb.Append("UE"); break; - - case 'é': - case 'è': - case 'ê': sb.Append('e'); break; - case 'á': - case 'à': - case 'â': sb.Append('a'); break; - case 'ú': - case 'ù': - case 'û': sb.Append('u'); break; - case 'ó': - case 'ò': - case 'ô': sb.Append('o'); break; - } // switch + if ((int)ch >= 128) + { + switch (ch) + { + case 'ä': sb.Append("ae"); break; + case 'ö': sb.Append("oe"); break; + case 'ü': sb.Append("ue"); break; + case 'ß': sb.Append("ss"); break; + case 'Ä': sb.Append("AE"); break; + case 'Ö': sb.Append("OE"); break; + case 'Ü': sb.Append("UE"); break; + default: + var c2 = ch.TryRemoveDiacritic(); + if ((c2 >= 'a' && c2 <= 'z') || (c2 >= 'A' && c2 <= 'Z')) + { + sb.Append(c2); + } + break; + } + } } // for if (sb.Length > 0) { res = sb.ToString().Trim(new char[] { ' ', '-' }); - Regex pat = new Regex(@"(-{2,})"); // remove double SpaceChar + Regex pat = new Regex(@"(-{2,})"); // remove double SpaceChar res = pat.Replace(res, "-"); res = res.Replace("__", "_"); } } } - catch (Exception exp) + catch (Exception ex) { - exp.Dump(); + ex.Dump(); } + return (res.Length > 0 ? res : "null"); } @@ -1034,16 +973,16 @@ public static string SanitizeHtmlId(this string value) public static string Sha(this string value, Encoding encoding) { - if (value.HasValue()) + if (!string.IsNullOrWhiteSpace(value)) { using (var sha1 = new SHA1CryptoServiceProvider()) { byte[] data = encoding.GetBytes(value); - return sha1.ComputeHash(data).ToHexString(); } } - return ""; + + return string.Empty; } [DebuggerStepThrough] @@ -1083,22 +1022,21 @@ public static string ToValidPath(this string input, string replacement = "-") private static string ToValidPathInternal(this string input, bool isPath, string replacement) { - var result = input.ToSafe(); - var invalidChars = new HashSet(isPath ? Path.GetInvalidPathChars() : Path.GetInvalidFileNameChars()); + replacement = replacement ?? "-"; + var sb = new StringBuilder(); - foreach (var c in input) + foreach (var c in input.ToSafe()) { if (invalidChars.Contains(c)) { - sb.Append(replacement ?? "-"); + sb.Append(replacement); } else { sb.Append(c); } - result = result.Replace(c.ToString(), replacement ?? "-"); } return sb.ToString(); @@ -1118,6 +1056,7 @@ public static bool ToIntArrayContains(this string s, int value, bool defaultValu var arr = s.ToIntArray(); if (arr == null || arr.Count() <= 0) return defaultValue; + return arr.Contains(value); } @@ -1147,24 +1086,24 @@ public static string ReplaceCsvChars(this string s) [DebuggerStepThrough] public static string HighlightKeywords(this string input, string keywords, string preMatch = "", string postMatch = "") { - Guard.NotNull(preMatch, nameof(preMatch)); - Guard.NotNull(postMatch, nameof(postMatch)); + Guard.NotNull(preMatch, nameof(preMatch)); + Guard.NotNull(postMatch, nameof(postMatch)); - if (input.IsEmpty() || keywords.IsEmpty()) + if (string.IsNullOrWhiteSpace(input) || string.IsNullOrWhiteSpace(keywords)) { return input; } var pattern = String.Join("|", keywords.Trim().Split(' ', '-') .Select(x => x.Trim()) - .Where(x => x.HasValue()) + .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => Regex.Escape(x)) .Distinct()); - if (pattern.HasValue()) + if (!string.IsNullOrWhiteSpace(pattern)) { var rg = new Regex(pattern, RegexOptions.IgnoreCase); - input = rg.Replace(input, m => preMatch + m.Value + postMatch); + input = rg.Replace(input, m => preMatch + m.Value.EmptyNull().HtmlEncode() + postMatch); } return input; diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index d2b270c7e5..b6832c9dee 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -15,9 +15,10 @@ public static class TypeExtensions public static string AssemblyQualifiedNameWithoutVersion(this Type type) { - Guard.NotNull(type, nameof(type)); + if (type == null) + throw new ArgumentNullException(nameof(type)); - if (type.AssemblyQualifiedName != null) + if (type.AssemblyQualifiedName != null) { var strArray = type.AssemblyQualifiedName.Split(new char[] { ',' }); return string.Format("{0}, {1}", strArray[0].Trim(), strArray[1].Trim()); @@ -26,7 +27,34 @@ public static string AssemblyQualifiedNameWithoutVersion(this Type type) return null; } - public static bool IsSequenceType(this Type type) + public static bool IsNumericType(this Type type) + { + switch (Type.GetTypeCode(type)) + { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + case TypeCode.Object: + if (type.IsNullable(out var innerType)) + { + return innerType.IsNumericType(); + } + return false; + default: + return false; + } + } + + public static bool IsSequenceType(this Type type) { if (type == typeof(string)) return false; @@ -120,9 +148,10 @@ public static bool IsNullable(this Type type, out Type wrappedType) public static bool IsConstructable(this Type type) { - Guard.NotNull(type, "type"); + if (type == null) + throw new ArgumentNullException(nameof(type)); - if (type.IsAbstract || type.IsInterface || type.IsArray || type.IsGenericTypeDefinition || type == typeof(void)) + if (type.IsAbstract || type.IsInterface || type.IsArray || type.IsGenericTypeDefinition || type == typeof(void)) return false; if (!HasDefaultConstructor(type)) @@ -154,9 +183,10 @@ public static bool IsAnonymous(this Type type) [DebuggerStepThrough] public static bool HasDefaultConstructor(this Type type) { - Guard.NotNull(type, nameof(type)); + if (type == null) + throw new ArgumentNullException(nameof(type)); - if (type.IsValueType) + if (type.IsValueType) return true; return type.GetConstructors(BindingFlags.Instance | BindingFlags.Public) @@ -165,16 +195,18 @@ public static bool HasDefaultConstructor(this Type type) public static bool IsSubClass(this Type type, Type check) { - Type implementingType; - return IsSubClass(type, check, out implementingType); - } + return IsSubClass(type, check, out Type implementingType); + } public static bool IsSubClass(this Type type, Type check, out Type implementingType) { - Guard.NotNull(type, "type"); - Guard.NotNull(check, "check"); + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (check == null) + throw new ArgumentNullException(nameof(check)); - return IsSubClassInternal(type, type, check, out implementingType); + return IsSubClassInternal(type, type, check, out implementingType); } private static bool IsSubClassInternal(Type initialType, Type currentType, Type check, out Type implementingType) @@ -249,6 +281,7 @@ public static TAttribute GetAttribute(this ICustomAttributeProvider { throw Error.MoreThanOneElement(); } + return (TAttribute)attributes[0]; } @@ -310,6 +343,7 @@ public static TAttribute[] GetAllAttributes(this MemberInfo member, } } } + attributes.AddRange(GetAttributes(member, inherits)); return attributes.ToArray(); } diff --git a/src/Libraries/SmartStore.Core/Fakes/FakeHttpContext.cs b/src/Libraries/SmartStore.Core/Fakes/FakeHttpContext.cs index 4a024630cc..68302e9a61 100644 --- a/src/Libraries/SmartStore.Core/Fakes/FakeHttpContext.cs +++ b/src/Libraries/SmartStore.Core/Fakes/FakeHttpContext.cs @@ -66,6 +66,8 @@ public FakeHttpContext(string relativeUrl, _serverVariables = serverVariables; _items = new Dictionary(); + + Handler = new FakeHttpHandler(); } public override IHttpHandler Handler { get; set; } diff --git a/src/Libraries/SmartStore.Core/Fakes/FakeHttpHandler.cs b/src/Libraries/SmartStore.Core/Fakes/FakeHttpHandler.cs new file mode 100644 index 0000000000..ea0839801a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Fakes/FakeHttpHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Web; + +namespace SmartStore.Core.Fakes +{ + public class FakeHttpHandler : IHttpHandler + { + public bool IsReusable => true; + + public void ProcessRequest(HttpContext context) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs b/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs index 5238183265..ee5ecc7928 100644 --- a/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs +++ b/src/Libraries/SmartStore.Core/Fakes/FakeHttpRequest.cs @@ -115,8 +115,8 @@ public override string ApplicationPath { get { - //we know that relative paths always start with ~/ - //ApplicationPath should start with / + // We know that relative paths always start with ~/ + // ApplicationPath should start with / if (_relativeUrl != null && _relativeUrl.StartsWith("~/")) return _relativeUrl.Remove(0, 1); return null; @@ -137,10 +137,11 @@ public override string UserHostAddress } public override string RawUrl => this.ApplicationPath; - public override bool IsSecureConnection => false; + public override bool IsSecureConnection => _url?.Scheme?.EmptyNull().StartsWith("https", StringComparison.OrdinalIgnoreCase) == true; public override bool IsAuthenticated => false; public override string[] UserLanguages => new string[] { }; public override string UserAgent => "SmartStore.NET"; + public override bool IsLocal => false; public override RequestContext RequestContext { diff --git a/src/Libraries/SmartStore.Core/Html/BBCodeHelper.cs b/src/Libraries/SmartStore.Core/Html/BBCodeHelper.cs index dea33ad5f2..b4e9c92eb0 100644 --- a/src/Libraries/SmartStore.Core/Html/BBCodeHelper.cs +++ b/src/Libraries/SmartStore.Core/Html/BBCodeHelper.cs @@ -4,21 +4,15 @@ namespace SmartStore.Core.Html { - /// - /// Represents a BBCode helper - /// public partial class BBCodeHelper { - #region Fields private static readonly Regex regexBold = new Regex(@"\[b\](.+?)\[/b\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexItalic = new Regex(@"\[i\](.+?)\[/i\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexUnderLine = new Regex(@"\[u\](.+?)\[/u\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexUrl1 = new Regex(@"\[url\=([^\]]+)\]([^\]]+)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexUrl2 = new Regex(@"\[url\](.+?)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexQuote = new Regex(@"\[quote(=.+?)?\](.+?)\[/quote\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - #endregion - #region Methods /// /// Formats the text /// @@ -30,8 +24,14 @@ public partial class BBCodeHelper /// A value indicating whether to replace Code /// A value indicating whether to replace Quote /// Formatted text - public static string FormatText(string text, bool replaceBold, bool replaceItalic, - bool replaceUnderline, bool replaceUrl, bool replaceCode, bool replaceQuote) + public static string ToHtml( + string text, + bool replaceBold = true, + bool replaceItalic = true, + bool replaceUnderline = true, + bool replaceUrl = true, + bool replaceCode = true, + bool replaceQuote = true) { if (String.IsNullOrEmpty(text)) return string.Empty; @@ -115,6 +115,5 @@ public static string RemoveQuotes(string str) str = Regex.Replace(str, @"\[/quote\]", String.Empty, RegexOptions.Compiled | RegexOptions.IgnoreCase); return str; } - #endregion } } diff --git a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs index 98e482d393..f3239a1b15 100644 --- a/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs +++ b/src/Libraries/SmartStore.Core/Html/HtmlUtils.cs @@ -6,6 +6,10 @@ using AngleSharp; using AngleSharp.Parser.Html; using AngleSharp.Parser.Css; +using System.Collections.Generic; +using AngleSharp.Dom; +using AngleSharp.Dom.Html; +using Ganss.XSS; namespace SmartStore.Core.Html { @@ -14,133 +18,127 @@ namespace SmartStore.Core.Html /// public partial class HtmlUtils { - private readonly static Regex _paragraphStartRegex = new Regex("

", RegexOptions.IgnoreCase); + private readonly static Regex _paragraphStartRegex = new Regex("

", RegexOptions.IgnoreCase); private readonly static Regex _paragraphEndRegex = new Regex("

", RegexOptions.IgnoreCase); - //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + //private static Regex ampRegex = new Regex("&(?!(?:#[0-9]{2,4};|[a-z0-9]+;))", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static string EnsureOnlyAllowedHtml(string text) - { - if (String.IsNullOrEmpty(text)) - return string.Empty; + /// + /// Formats the text + /// + /// Text + /// A value indicating whether to strip tags + /// A value indicating whether HTML is allowed + /// A value indicating whether HTML is allowed + /// A value indicating whether BBCode is allowed + /// A value indicating whether to resolve links + /// A value indicating whether to add "noFollow" tag + /// Formatted text + [Obsolete("Use specific formatter methods instead, e.g. StripTags(), SanitizeHtml() etc.")] + public static string FormatText( + string text, + bool stripTags, + bool convertPlainTextToHtml, + bool allowHtml, + bool allowBBCode, + bool resolveLinks, + bool addNoFollowTag) + { - string allowedTags = "br,hr,b,i,u,a,div,ol,ul,li,dl,dt,dd,blockquote,img,span,p,em,strong,font,pre,h1,h2,h3,h4,h5,h6,address,cite"; + if (String.IsNullOrEmpty(text)) + return string.Empty; - var m = Regex.Matches(text, "<.*?>", RegexOptions.IgnoreCase); - for (int i = m.Count - 1; i >= 0; i--) - { - string tag = text.Substring(m[i].Index + 1, m[i].Length - 1).Trim().ToLower(); + try + { + if (stripTags) + { + text = StripTags(text); + } - if (!IsValidTag(tag, allowedTags)) - { - text = text.Remove(m[i].Index, m[i].Length); - } - } + if (allowHtml) + { + text = SanitizeHtml(text); + } + else + { + text = HttpUtility.HtmlEncode(text); + } - return text; - } + if (convertPlainTextToHtml) + { + text = ConvertPlainTextToHtml(text); + } - private static bool IsValidTag(string tag, string tags) - { - string[] allowedTags = tags.Split(','); - if (tag.IndexOf("javascript", StringComparison.OrdinalIgnoreCase) >= 0) return false; - if (tag.IndexOf("vbscript", StringComparison.OrdinalIgnoreCase) >= 0) return false; - if (tag.IndexOf("onclick", StringComparison.OrdinalIgnoreCase) >= 0) return false; + if (allowBBCode) + { + text = BBCodeHelper.ToHtml(text, true, true, true, true, true, true); + } - var endchars = new char[] { ' ', '>', '/', '\t' }; + if (resolveLinks) + { + text = ResolveLinksHelper.FormatText(text); + } - int pos = tag.IndexOfAny(endchars, 1); - if (pos > 0) tag = tag.Substring(0, pos); - if (tag[0] == '/') tag = tag.Substring(1); + if (addNoFollowTag) + { + //add noFollow tag. not implemented + } + } + catch (Exception exc) + { + text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); + } - return allowedTags.Any(aTag => tag == aTag); - } + return text; + } - /// - /// Formats the text - /// - /// Text - /// A value indicating whether to strip tags - /// A value indicating whether HTML is allowed - /// A value indicating whether HTML is allowed - /// A value indicating whether BBCode is allowed - /// A value indicating whether to resolve links - /// A value indicating whether to add "noFollow" tag - /// Formatted text - public static string FormatText( - string text, - bool stripTags, - bool convertPlainTextToHtml, - bool allowHtml, - bool allowBBCode, - bool resolveLinks, - bool addNoFollowTag) + public static string SanitizeHtml(string html, bool isFragment = true) { - - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(html)) return string.Empty; - try - { - if (stripTags) - { - text = StripTags(text); - } - - if (allowHtml) - { - text = EnsureOnlyAllowedHtml(text); - } - else - { - text = HttpUtility.HtmlEncode(text); - } - - if (convertPlainTextToHtml) - { - text = ConvertPlainTextToHtml(text); - } - - if (allowBBCode) - { - text = BBCodeHelper.FormatText(text, true, true, true, true, true, true); - } + var sanitizer = new HtmlSanitizer(); - if (resolveLinks) - { - text = ResolveLinksHelper.FormatText(text); - } - - if (addNoFollowTag) - { - //add noFollow tag. not implemented - } - } - catch (Exception exc) - { - text = string.Format("Text cannot be formatted. Error: {0}", exc.Message); - } - return text; + if (isFragment) + { + return sanitizer.Sanitize(html); + } + else + { + return sanitizer.SanitizeDocument(html); + } } /// /// Strips tags /// - /// Text + /// Text /// Formatted text - public static string StripTags(string text) + public static string StripTags(string html) { - if (String.IsNullOrEmpty(text)) - return string.Empty; + if (html.IsEmpty()) + return string.Empty; - text = Regex.Replace(text, @"(>)(\r|\n)*(<)", "><"); - text = Regex.Replace(text, "(<[^>]*>)([^<]*)", "$2"); - text = Regex.Replace(text, "(&#x?[0-9]{2,4};|"|&| |<|>|€|©|®|‰|‡|†|‹|›|„|”|“|‚|’|‘|—|–|‏|‎|‍|‌| | | |˜|ˆ|Ÿ|š|Š)", "@"); + var ignoreTags = new HashSet(StringComparer.OrdinalIgnoreCase) { "script", "style", "svg", "img" }; + var parser = new HtmlParser(); + var doc = parser.Parse(html); - return text; - } + List removeElements = new List(); + + foreach (var ignoreTag in ignoreTags) + { + removeElements.AddRange(doc.QuerySelectorAll(ignoreTag)); + } + + foreach (var el in removeElements) + { + el.Remove(); + } + + return doc.Body.TextContent; + } /// - /// replace anchor text (remove a tag from the following url Name and output only the string "Name") + /// Replace anchor text (remove a tag from the following url Name and output only the string "Name") /// /// Text /// Text @@ -160,7 +158,7 @@ public static string ReplaceAnchorTags(string text) /// Formatted text public static string ConvertPlainTextToHtml(string text) { - if (String.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) return string.Empty; text = text.Replace("\r\n", "
"); @@ -294,7 +292,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/Html/ResolveLinksHelper.cs b/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs index c58f9540ba..afa070448f 100644 --- a/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs +++ b/src/Libraries/SmartStore.Core/Html/ResolveLinksHelper.cs @@ -10,16 +10,12 @@ namespace SmartStore.Core.Html ///
public partial class ResolveLinksHelper { - #region Fields /// /// The regular expression used to parse links. /// private static readonly Regex regex = new Regex("((http://|https://|www\\.)([A-Z0-9.\\-]{1,})\\.[0-9A-Z?;~&\\(\\)#,=\\-_\\./\\+]{2,})", RegexOptions.Compiled | RegexOptions.IgnoreCase); private const string link = "{2}"; private const int MAX_LENGTH = 50; - #endregion - - #region Utilities /// /// Shortens any absolute URL to a specified maximum length @@ -80,9 +76,7 @@ private static string ShortenUrl(string url, int max) return url; } - #endregion - #region Methods /// /// Formats the text /// @@ -93,21 +87,20 @@ public static string FormatText(string text) if (String.IsNullOrEmpty(text)) return string.Empty; - var info = CultureInfo.InvariantCulture; + var ci = CultureInfo.InvariantCulture; foreach (Match match in regex.Matches(text)) { if (!match.Value.Contains("://")) { - text = text.Replace(match.Value, string.Format(info, link, "http://", match.Value, ShortenUrl(match.Value, MAX_LENGTH))); + text = text.Replace(match.Value, string.Format(ci, link, "http://", match.Value, ShortenUrl(match.Value, MAX_LENGTH))); } else { - text = text.Replace(match.Value, string.Format(info, link, string.Empty, match.Value, ShortenUrl(match.Value, MAX_LENGTH))); + text = text.Replace(match.Value, string.Format(ci, link, string.Empty, match.Value, ShortenUrl(match.Value, MAX_LENGTH))); } } return text; } - #endregion } } 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..9591f17d81 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; @@ -30,7 +31,7 @@ protected internal LocalFileSystem(string basePath, string publicPath) { basePath = basePath.EmptyNull(); - var pathIsAbsolute = FileSystemHelper.IsFullPath(basePath); + var pathIsAbsolute = PathHelper.IsAbsolutePhysicalPath(basePath); NormalizeStoragePath(ref basePath, pathIsAbsolute); @@ -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; } @@ -491,14 +517,35 @@ public long Size public DateTime LastUpdated { - get { return _fileInfo.LastWriteTime; } + get { return _fileInfo.LastWriteTimeUtc; } } - 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; } @@ -548,7 +595,7 @@ public string Name public DateTime LastUpdated { - get { return _directoryInfo.LastWriteTime; } + get { return _directoryInfo.LastWriteTimeUtc; } } public long Size 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..20d00e3441 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 @@ -41,7 +42,7 @@ public bool TryAcquireLock(string path, out ILockFile lockFile) { return false; } - + lockFile = new LockFile(_env.TenantFolder, path, DateTime.UtcNow.ToString("u"), _rwLock); return true; } @@ -74,12 +75,12 @@ 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); - DateTime creationUtc; - if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out creationUtc)) + if (DateTime.TryParse(content, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var creationUtc)) { // if expired the file is not removed // it should be automatically as there is a finalizer in LockFile diff --git a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs index d9edddeef5..02ecb8dd82 100644 --- a/src/Libraries/SmartStore.Core/IO/MimeTypes.cs +++ b/src/Libraries/SmartStore.Core/IO/MimeTypes.cs @@ -1,19 +1,765 @@ using System; -using System.Collections.Concurrent; +using System.IO; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Web; using Microsoft.Win32; 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 +773,34 @@ 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; + } + + 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/IVirtualFolder.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs index 6f1bb5f3a2..e4177560ad 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/IVirtualFolder.cs @@ -33,6 +33,7 @@ public interface IVirtualFolder Stream OpenFile(string relativePath); void CreateTextFile(string relativePath, string content); + Task CreateTextFileAsync(string relativePath, string content); Stream CreateFile(string relativePath); void CreateDirectory(string relativePath); @@ -40,6 +41,7 @@ public interface IVirtualFolder void DeleteDirectory(string relativePath); string ReadFile(string relativePath); + Task ReadFileAsync(string relativePath); void CopyFile(string relativePath, Stream destination); DateTime GetFileLastWriteTimeUtc(string relativePath); diff --git a/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs index 7db7ae9f7c..6355143bc6 100644 --- a/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs +++ b/src/Libraries/SmartStore.Core/IO/VirtualPath/VirtualFolder.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using SmartStore.Core.Infrastructure.DependencyManagement; +using System.Threading.Tasks; using SmartStore.Core.Logging; namespace SmartStore.Core.IO @@ -118,6 +118,19 @@ public void CreateTextFile(string relativePath, string content) } } + public async Task CreateTextFileAsync(string relativePath, string content) + { + Guard.NotEmpty(relativePath, nameof(relativePath)); + + using (var stream = CreateFile(relativePath)) + { + using (var tw = new StreamWriter(stream)) + { + await tw.WriteAsync(content); + } + } + } + public virtual Stream CreateFile(string relativePath) { var filePath = MapPath(relativePath); @@ -171,6 +184,26 @@ public virtual string ReadFile(string relativePath) } } + public virtual async Task ReadFileAsync(string relativePath) + { + Guard.NotEmpty(relativePath, nameof(relativePath)); + + var path = GetVirtualPath(relativePath); + + if (!_vpp.FileExists(path)) + { + return null; + } + + using (var stream = _vpp.OpenFile(path)) + { + using (var reader = new StreamReader(stream)) + { + return await reader.ReadToEndAsync(); + } + } + } + public virtual void CopyFile(string relativePath, Stream destination) { Guard.NotEmpty(relativePath, nameof(relativePath)); diff --git a/src/Libraries/SmartStore.Core/IPageable.cs b/src/Libraries/SmartStore.Core/IPageable.cs index ef3ce500a2..ce270c8e4e 100644 --- a/src/Libraries/SmartStore.Core/IPageable.cs +++ b/src/Libraries/SmartStore.Core/IPageable.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace SmartStore.Core { @@ -82,10 +83,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 /// @@ -93,7 +101,19 @@ public interface IPagedList : IPageable, IList /// A query with applied paging args IQueryable ApplyPaging(IQueryable query); + /// + /// Loads the data synchronously. + /// + /// When true, always reloads data. When false, first checks to see whether data has been loaded already and skips if so. + /// Returns itself for chaining. IPagedList Load(bool force = false); + + /// + /// Loads the data asynchronously. + /// + /// When true, always reloads data. When false, first checks to see whether data has been loaded already and skips if so. + /// Returns itself for chaining. + Task> LoadAsync(bool force = false); } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs index b7ffec8f69..4ed72318a1 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs @@ -71,7 +71,7 @@ public IEnumerable FindClassesOfType(Type assignTypeFrom, IEnumerable : 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/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs index 89a23a352d..f101841492 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/AutofacRequestLifetimeHttpModule.cs @@ -59,11 +59,7 @@ private static void PurgeContextItems(HttpApplication app) public static void SetLifetimeScopeProvider(ILifetimeScopeProvider lifetimeScopeProvider) { - if (lifetimeScopeProvider == null) - { - throw new ArgumentNullException("lifetimeScopeProvider"); - } - LifetimeScopeProvider = lifetimeScopeProvider; + LifetimeScopeProvider = lifetimeScopeProvider ?? throw new ArgumentNullException("lifetimeScopeProvider"); } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index ddeac694ca..8a934935e0 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Autofac; @@ -26,36 +27,43 @@ public IContainer Container get { return _container; } } - public T Resolve(object key = null, ILifetimeScope scope = null) where T : class + [DebuggerStepThrough] + public T Resolve(object key = null, ILifetimeScope scope = null) where T : class { if (key == null) { return (scope ?? Scope()).Resolve(); } + return (scope ?? Scope()).ResolveKeyed(key); } - public T ResolveNamed(string name, ILifetimeScope scope = null) where T : class + [DebuggerStepThrough] + public T ResolveNamed(string name, ILifetimeScope scope = null) where T : class { return (scope ?? Scope()).ResolveNamed(name); } - public object Resolve(Type type, ILifetimeScope scope = null) + [DebuggerStepThrough] + public object Resolve(Type type, ILifetimeScope scope = null) { return (scope ?? Scope()).Resolve(type); } - public object ResolveNamed(string name, Type type, ILifetimeScope scope = null) + [DebuggerStepThrough] + public object ResolveNamed(string name, Type type, ILifetimeScope scope = null) { return (scope ?? Scope()).ResolveNamed(name, type); } - public T[] ResolveAll(object key = null, ILifetimeScope scope = null) + [DebuggerStepThrough] + public T[] ResolveAll(object key = null, ILifetimeScope scope = null) { if (key == null) { return (scope ?? Scope()).Resolve>().ToArray(); } + return (scope ?? Scope()).ResolveKeyed>(key).ToArray(); } @@ -66,10 +74,9 @@ public T ResolveUnregistered(ILifetimeScope scope = null) where T : class public object ResolveUnregistered(Type type, ILifetimeScope scope = null) { - FastActivator activator; object[] parameterInstances = null; - if (!_cachedActivators.TryGetValue(type, out activator)) + if (!_cachedActivators.TryGetValue(type, out FastActivator activator)) { var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var constructor in constructors) @@ -129,7 +136,8 @@ private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope } } - public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) + [DebuggerStepThrough] + public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) { instance = null; @@ -143,7 +151,8 @@ public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instan } } - public bool TryResolve(ILifetimeScope scope, out T instance) + [DebuggerStepThrough] + public bool TryResolve(ILifetimeScope scope, out T instance) { instance = default(T); @@ -177,6 +186,7 @@ public T InjectUnsetProperties(T instance, ILifetimeScope scope = null) return (scope ?? Scope()).InjectUnsetProperties(instance); } + [DebuggerStepThrough] public ILifetimeScope Scope() { var scope = _container.Resolve().GetLifetimeScope(null); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs index bf84c53263..1c7fed864c 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/DefaultLifetimeScopeAccessor.cs @@ -61,6 +61,7 @@ public ILifetimeScope GetLifetimeScope(Action configurationAct _state.SetState((scope = BeginLifetimeScope(configurationAction))); //scope.CurrentScopeEnding += OnScopeEnding; } + return scope; } @@ -76,7 +77,7 @@ public ILifetimeScope GetLifetimeScope(Action configurationAct // Debug.WriteLine("Scope END, Web: " + isWeb); //} - private ILifetimeScope BeginLifetimeScope(Action configurationAction) + public ILifetimeScope BeginLifetimeScope(Action configurationAction) { return (configurationAction == null) ? _rootContainer.BeginLifetimeScope(ScopeTag) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs index 03beda80b8..ee09073226 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ILifetimeScopeAccessor.cs @@ -39,5 +39,14 @@ public interface ILifetimeScopeAccessor /// desirable to create a new scope, but use the existing, http context bound scope instead. /// IDisposable BeginContextAwareScope(); + + /// + /// Creates a nested lifetime scope that services can be resolved from + /// + /// A configuration action that will execute during lifetime scope creation. + /// + /// A new nested lifetime scope. + /// + ILifetimeScope BeginLifetimeScope(Action configurationAction); } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs index 2499930000..a1d8f6c73a 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Guard.cs @@ -8,17 +8,16 @@ namespace SmartStore { - public class Guard { - private const string AgainstMessage = "Assertion evaluation failed with 'false'."; - private const string ImplementsMessage = "Type '{0}' must implement type '{1}'."; - private const string InheritsFromMessage = "Type '{0}' must inherit from type '{1}'."; - private const string IsTypeOfMessage = "Type '{0}' must be of type '{1}'."; - private const string IsEqualMessage = "Compared objects must be equal."; - private const string IsPositiveMessage = "Argument '{0}' must be a positive value. Value: '{1}'."; - private const string IsTrueMessage = "True expected for '{0}' but the condition was False."; - private const string NotNegativeMessage = "Argument '{0}' cannot be a negative value. Value: '{1}'."; + internal const string AgainstMessage = "Assertion evaluation failed with 'false'."; + internal const string ImplementsMessage = "Type '{0}' must implement type '{1}'."; + internal const string InheritsFromMessage = "Type '{0}' must inherit from type '{1}'."; + internal const string IsTypeOfMessage = "Type '{0}' must be of type '{1}'."; + internal const string IsEqualMessage = "Compared objects must be equal."; + internal const string IsPositiveMessage = "Argument '{0}' must be a positive value. Value: '{1}'."; + internal const string IsTrueMessage = "True expected for '{0}' but the condition was False."; + internal const string NotNegativeMessage = "Argument '{0}' cannot be a negative value. Value: '{1}'."; private Guard() { @@ -36,7 +35,7 @@ public static void NotNull(object arg, string argName) [DebuggerStepThrough] public static void NotEmpty(string arg, string argName) { - if (arg.IsEmpty()) + if (string.IsNullOrWhiteSpace(arg)) throw Error.Argument(argName, "String parameter '{0}' cannot be null or all whitespace.", argName); } @@ -44,7 +43,7 @@ public static void NotEmpty(string arg, string argName) public static void NotEmpty(ICollection arg, string argName) { if (arg == null || !arg.Any()) - throw Error.Argument(argName, "Collection cannot be null and must have at least one item."); + throw Error.Argument(argName, "Collection cannot be null and must contain at least one item."); } [DebuggerStepThrough] @@ -117,6 +116,7 @@ public static void IsTrue(bool arg, string argName, string message = IsTrueMessa public static void IsEnumType(Type arg, string argName) { NotNull(arg, argName); + if (!arg.IsEnum) throw Error.Argument(argName, "Type '{0}' must be a valid Enum type.", arg.FullName); } @@ -125,6 +125,7 @@ public static void IsEnumType(Type arg, string argName) public static void IsEnumType(Type enumType, object arg, string argName) { NotNull(arg, argName); + if (!Enum.IsDefined(enumType, arg)) { throw Error.ArgumentOutOfRange(argName, "The value of the argument '{0}' provided for the enumeration '{1}' is invalid.", argName, enumType.FullName); @@ -135,6 +136,7 @@ public static void IsEnumType(Type enumType, object arg, string argName) public static void NotDisposed(DisposableObject arg, string argName) { NotNull(arg, argName); + if (arg.IsDisposed) throw Error.ObjectDisposed(argName); } @@ -143,6 +145,7 @@ public static void NotDisposed(DisposableObject arg, string argName) public static void PagingArgsValid(int indexArg, int sizeArg, string indexArgName, string sizeArgName) { NotNegative(indexArg, indexArgName, "PageIndex cannot be below 0"); + if (indexArg > 0) { // if pageIndex is specified (> 0), PageSize CANNOT be 0 diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Singleton.cs b/src/Libraries/SmartStore.Core/Infrastructure/Singleton.cs index 9307146c93..66c50bd5fa 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Singleton.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Singleton.cs @@ -9,7 +9,7 @@ namespace SmartStore.Core.Infrastructure /// sense of the word as a standardized way to store single instances. /// /// The type of object to store. - /// Access to the instance is not synchrnoized. + /// Access to the instance is not synchronized. public class Singleton : Singleton { static T instance; diff --git a/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs b/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs index 18dcfc4222..0029454b28 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/SmartStoreEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Web.Mvc; @@ -148,6 +149,7 @@ public bool IsFullyInitialized set; } + [DebuggerStepThrough] public T Resolve(string name = null) where T : class { if (name.HasValue()) @@ -157,6 +159,7 @@ public T Resolve(string name = null) where T : class return ContainerManager.Resolve(); } + [DebuggerStepThrough] public object Resolve(Type type, string name = null) { if (name.HasValue()) @@ -166,7 +169,7 @@ public object Resolve(Type type, string name = null) return ContainerManager.Resolve(type); } - + [DebuggerStepThrough] public T[] ResolveAll() { return ContainerManager.ResolveAll(); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs index f06b87c086..a437eebd7d 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/WebAppTypeFinder.cs @@ -95,7 +95,7 @@ protected virtual void LoadPluginAssemblies() AppDomain.CurrentDomain.GetAssemblies().Select(x => x.FullName), StringComparer.OrdinalIgnoreCase); - var pluginAssemblies = new HashSet(PluginManager.ReferencedPlugins.Select(x => x.ReferencedAssembly).Where(x => x != null)); + var pluginAssemblies = new HashSet(PluginManager.ReferencedPlugins.Select(x => x.Assembly.Assembly)); foreach (var assembly in pluginAssemblies) { 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/Localization/LocalizedString.cs b/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs index a09c159735..5965d20b51 100644 --- a/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs +++ b/src/Libraries/SmartStore.Core/Localization/LocalizedString.cs @@ -26,6 +26,7 @@ public static LocalizedString TextOrDefault(string text, LocalizedString default { if (string.IsNullOrEmpty(text)) return defaultValue; + return new LocalizedString(text); } diff --git a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs index 1b043a2108..6bd1f49e02 100644 --- a/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/log4net/Log4netLogger.cs @@ -74,13 +74,12 @@ public void Log(LogLevel level, Exception exception, string message, object[] ar protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) { - HttpRequest httpRequest = null; + var isAppInitialized = EngineContext.Current.IsFullyInitialized; - try - { - httpRequest = HttpContext.Current.Request; - } - catch + // Don't knowingly run into exception + var httpRequest = isAppInitialized ? HttpContext.Current.SafeGetHttpRequest() : null; + + if (httpRequest == null) { loggingEvent.Properties["CustomerId"] = DBNull.Value; loggingEvent.Properties["Url"] = DBNull.Value; @@ -100,14 +99,12 @@ protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) { using (new ActionDisposable(() => props["sm:ThreadInfoAdded"] = true)) { - if (DataSettings.DatabaseIsInstalled() && EngineContext.Current.IsFullyInitialized) + if (DataSettings.DatabaseIsInstalled() && isAppInitialized) { var container = EngineContext.Current.ContainerManager; - IWorkContext workContext; - // CustomerId - if (container.TryResolve(null, out workContext)) + if (container.TryResolve(null, out IWorkContext workContext)) { try { @@ -119,10 +116,9 @@ protected internal void TryAddExtendedThreadInfo(LoggingEvent loggingEvent) } } - IWebHelper webHelper; // Url & stuff - if (container.TryResolve(null, out webHelper)) + if (container.TryResolve(null, out IWebHelper webHelper)) { try { diff --git a/src/Libraries/SmartStore.Core/Money.cs b/src/Libraries/SmartStore.Core/Money.cs new file mode 100644 index 0000000000..33244b92ef --- /dev/null +++ b/src/Libraries/SmartStore.Core/Money.cs @@ -0,0 +1,483 @@ +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) + { + // Prevent stack overflow. + if (obj != null) + { + return Equals(obj as Money); + } + + return false; + } + + 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/Packaging/Updater/AppUpdater.cs b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs index cbda7cdb18..a645c43d9a 100644 --- a/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs +++ b/src/Libraries/SmartStore.Core/Packaging/Updater/AppUpdater.cs @@ -1,23 +1,19 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; -using System.IO; -using SmartStore.Utilities; -using SmartStore.Utilities.Threading; -using SmartStore.Core.Logging; -using Log = SmartStore.Core.Logging; using NuGet; -using NuGetPackageManager = NuGet.PackageManager; using SmartStore.Core.Data; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; +using SmartStore.Utilities; +using SmartStore.Utilities.Threading; namespace SmartStore.Core.Packaging { - public sealed class AppUpdater : DisposableObject + public sealed class AppUpdater : DisposableObject { public const string UpdatePackagePath = "~/App_Data/Update"; @@ -214,7 +210,6 @@ private PackageInfo ExecuteUpdate(IPackage package) #endregion - #region Migrations internal void ExecuteMigrations() @@ -227,17 +222,23 @@ internal void ExecuteMigrations() var currentVersion = SmartStoreVersion.Version; var prevVersion = DataSettings.Current.AppVersion ?? new Version(1, 0); - if (prevVersion >= currentVersion) + if (prevVersion >= currentVersion) return; - if (prevVersion < new Version(2, 1)) - { - // we introduced app migrations in V2.1. So any version prior 2.1 - // has to perform the initial migration - MigrateInitial(); - } + if (prevVersion < new Version(2, 1)) + { + // we introduced app migrations in V2.1. So any version prior 2.1 + // has to perform the initial migration + MigrateInitial(); + } + + if (prevVersion <= new Version(3, 1, 5, 0)) + { + // We updated to Lucene.Net 4.8. + DeleteSearchIndex(); + } - DataSettings.Current.AppVersion = currentVersion; + DataSettings.Current.AppVersion = currentVersion; DataSettings.Current.Save(); } @@ -415,8 +416,32 @@ private void MigrateInitial() PluginFileParser.SaveInstalledPluginsFile(renamedPlugins); } - #endregion + private void DeleteSearchIndex() + { + var tenantPath = CommonHelper.MapPath(DataSettings.Current.TenantPath); + + try + { + var indexingDir = new DirectoryInfo(Path.Combine(tenantPath, "Indexing")); + if (indexingDir.Exists) + { + indexingDir.Delete(true); + } + } + catch { } + + try + { + var luceneDir = new DirectoryInfo(Path.Combine(tenantPath, "Lucene")); + if (luceneDir.Exists) + { + luceneDir.Delete(true); + } + } + catch { } + } + #endregion protected override void OnDispose(bool disposing) { diff --git a/src/Libraries/SmartStore.Core/PagedList`T.cs b/src/Libraries/SmartStore.Core/PagedList`T.cs index 1614557a1e..0fa22d90e1 100644 --- a/src/Libraries/SmartStore.Core/PagedList`T.cs +++ b/src/Libraries/SmartStore.Core/PagedList`T.cs @@ -5,16 +5,14 @@ using System.Data.Entity.Infrastructure; using System.Collections; using System.Collections.ObjectModel; +using System.Threading.Tasks; namespace SmartStore.Core { public class PagedList : IPagedList, IReadOnlyList, IReadOnlyCollection { - private IQueryable _query; + private bool _isEfQuery; private bool _queryIsPagedAlready; - - private int _pageIndex; - private int _pageSize; private int? _totalCount; private List _list; @@ -40,12 +38,13 @@ private void Init(IQueryable source, int pageIndex, int pageSize, int? totalC Guard.NotNull(source, nameof(source)); Guard.PagingArgsValid(pageIndex, pageSize, "pageIndex", "pageSize"); - _query = source; - _queryIsPagedAlready = totalCount.HasValue; + SourceQuery = source; + PageIndex = pageIndex; + PageSize = pageSize; - _pageIndex = pageIndex; - _pageSize = pageSize; _totalCount = totalCount; + _queryIsPagedAlready = totalCount.HasValue; + _isEfQuery = source.Provider is IDbAsyncQueryProvider; } private void EnsureIsLoaded() @@ -54,50 +53,76 @@ private void EnsureIsLoaded() { if (_totalCount == null) { - _totalCount = _query.Count(); + _totalCount = SourceQuery.Count(); } if (_queryIsPagedAlready) { - _list = _query.ToList(); + _list = SourceQuery.ToList(); } else { - _list = ApplyPaging(_query).ToList(); + _list = ApplyPaging(SourceQuery).ToList(); + } + } + } + + private async Task EnsureIsLoadedAsync() + { + if (_list == null) + { + if (_totalCount == null) + { + _totalCount = SourceQuery.Count(); + } + + if (_queryIsPagedAlready) + { + _list = await SourceQuery.ToListAsync(); + } + else + { + _list = await ApplyPaging(SourceQuery).ToListAsync(); } } } #region IPageable Members - public IQueryable SourceQuery + public IQueryable SourceQuery { get; private set; } + + public IPagedList AlterQuery(Func, IQueryable> alterer) { - get { return _query; } + var result = alterer?.Invoke(SourceQuery); + SourceQuery = 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) + if (PageIndex == 0 && PageSize == int.MaxValue) { // Paging unnecessary return query; } else { - var skip = _pageIndex * _pageSize; - if (query.Provider is IDbAsyncQueryProvider) + var skip = PageIndex * PageSize; + if (_isEfQuery) { - return query.Skip(() => skip).Take(() => _pageSize); + return query.Skip(() => skip).Take(() => PageSize); } else { - return query.Skip(skip).Take(_pageSize); + return query.Skip(skip).Take(PageSize); } } } public IPagedList Load(bool force = false) { + // Returns instance for chaining. if (force && _list != null) { _list.Clear(); @@ -109,25 +134,31 @@ public IPagedList Load(bool force = false) return this; } - public int PageIndex - { - get { return _pageIndex; } - set { _pageIndex = value; } - } + public async Task> LoadAsync(bool force = false) + { + // Returns instance for chaining. + if (force && _list != null) + { + _list.Clear(); + _list = null; + } - public int PageSize - { - get { return _pageSize; } - set { _pageSize = value; } + await EnsureIsLoadedAsync(); + + return this; } - public int TotalCount + public int PageIndex { get; set; } + + public int PageSize { get; set; } + + public int TotalCount { get { if (!_totalCount.HasValue) { - _totalCount = _query.Count(); + _totalCount = SourceQuery.Count(); } return _totalCount.Value; diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs index 240a788883..ab6a32754e 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginDescriptor.cs @@ -11,6 +11,30 @@ namespace SmartStore.Core.Plugins { + /// + /// Information about an assembly referenced by a plugin. + /// + public sealed class AssemblyReference + { + /// + /// File in ASP.NET temp folder + /// + public FileInfo File { get; internal set; } + /// + /// The original assembly file that a shadow copy was made from it + /// + public FileInfo OriginalFile { get; internal set; } + /// + /// The assembly that has been shadow copied and was loaded into the AppDomain + /// + public Assembly Assembly { get; internal set; } + + /// + /// Exception thrown during activation/probing + /// + internal Exception ActivationException { get; set; } + } + [DataContract] public class PluginDescriptor : IComparable { @@ -19,17 +43,16 @@ public class PluginDescriptor : IComparable public PluginDescriptor() { - this.Version = new Version("1.0"); - this.MinAppVersion = SmartStoreVersion.Version; + Version = new Version("1.0"); + MinAppVersion = SmartStoreVersion.Version; } // Unit tests - public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFile, Type pluginType) + public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFile, Type pluginClrType) : this() { - this.ReferencedAssembly = referencedAssembly; - this.OriginalAssemblyFile = originalAssemblyFile; - this.PluginType = pluginType; + Assembly = new AssemblyReference { Assembly = referencedAssembly, OriginalFile = originalAssemblyFile, File = originalAssemblyFile }; + PluginClrType = pluginClrType; } /// @@ -42,11 +65,22 @@ 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 + /// + /// Gets the folder name + /// + [DataMember] + public string FolderName { get; internal set; } + + /// + /// 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 { @@ -74,33 +108,23 @@ public string BrandImageFileName } /// - /// Plugin type - /// - public Type PluginType { get; set; } - - /// - /// The assembly that has been shadow copied and was loaded into the AppDomain + /// Plugin runtime type /// - public Assembly ReferencedAssembly { get; internal set; } - - /// - /// The original assembly file that a shadow copy was made from it - /// - public FileInfo OriginalAssemblyFile { get; internal set; } + public Type PluginClrType { get; set; } /// - /// The list of assembly files found in the plugin folder, except the main plugin assembly. + /// The plugin assembly. /// - public IEnumerable ReferencedLocalAssemblyFiles { get; internal set; } + public AssemblyReference Assembly { get; internal set; } = new AssemblyReference(); /// - /// Gets any exception thrown during plugin activation & initializing + /// List of assemblies files found in the plugin folder, except the main plugin assembly. /// - public Exception ActivationException { get; internal set; } + internal AssemblyReference[] ReferencedLocalAssemblies { get; set; } = new AssemblyReference[0]; - /// - /// Gets or sets the plugin group - /// + /// + /// Gets or sets the plugin group + /// [DataMember] public string Group { get; internal set; } @@ -118,12 +142,6 @@ public bool IsInKnownGroup [DataMember] public string FriendlyName { get; set; } - /// - /// Gets the folder name - /// - [DataMember] - public string FolderName { get; internal set; } - /// /// Gets or sets the system name /// @@ -238,10 +256,10 @@ public string ResourceRootKey public T Instance() where T : class, IPlugin { object instance; - if (!EngineContext.Current.ContainerManager.TryResolve(PluginType, null, out instance)) + if (!EngineContext.Current.ContainerManager.TryResolve(PluginClrType, null, out instance)) { // Not registered - instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginType); + instance = EngineContext.Current.ContainerManager.ResolveUnregistered(PluginClrType); } var typedInstance = instance as T; diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs b/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs index 07fcd1de9e..10e0ce284f 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginFileParser.cs @@ -42,7 +42,9 @@ public override int Compare(string x, string y) "Sales", "Design", "Performance", - "Misc" + "B2B", + "Storefront", + "Law" }; public readonly static IComparer KnownGroupComparer = new GroupComparer(); diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs b/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs index 8e61d47f37..4eddd033dd 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginFinder.cs @@ -1,32 +1,33 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace SmartStore.Core.Plugins { - /// - /// Plugin finder - /// public class PluginFinder : IPluginFinder { private IList _plugins; private readonly IDictionary _nameMap = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private readonly IDictionary _assemblyMap = new Dictionary(); - private static readonly object s_lock = new object(); + private static readonly object _lock = new object(); - public PluginFinder() + private PluginFinder() { - lock (s_lock) + lock (_lock) { LoadPlugins(); } } + public static IPluginFinder Current { get; } = new PluginFinder(); + private void LoadPlugins() { + if (_plugins != null) + return; + var plugins = PluginManager.ReferencedPlugins.ToList(); plugins.Sort(); //sort _plugins = plugins; @@ -34,7 +35,10 @@ private void LoadPlugins() foreach (var plugin in plugins) { _nameMap[plugin.SystemName] = plugin; - _assemblyMap[plugin.ReferencedAssembly] = plugin; + if (plugin.Assembly.Assembly != null) + { + _assemblyMap[plugin.Assembly.Assembly] = plugin; + } } } @@ -47,7 +51,7 @@ private void LoadPlugins() public virtual IEnumerable GetPlugins(bool installedOnly = true) where T : class, IPlugin { foreach (var plugin in _plugins) - if (typeof(T).IsAssignableFrom(plugin.PluginType)) + if (typeof(T).IsAssignableFrom(plugin.PluginClrType)) if (!installedOnly || plugin.Installed) yield return plugin.Instance(); } @@ -74,15 +78,14 @@ public virtual IEnumerable GetPluginDescriptors(bool instal where T : class, IPlugin { foreach (var plugin in _plugins) - if (typeof(T).IsAssignableFrom(plugin.PluginType)) + if (typeof(T).IsAssignableFrom(plugin.PluginClrType)) if (!installedOnly || plugin.Installed) yield return plugin; } public virtual PluginDescriptor GetPluginDescriptorByAssembly(Assembly assembly, bool installedOnly = true) { - PluginDescriptor descriptor; - if (assembly != null && _assemblyMap.TryGetValue(assembly, out descriptor)) + if (assembly != null && _assemblyMap.TryGetValue(assembly, out var descriptor)) { if (!installedOnly || descriptor.Installed) return descriptor; @@ -99,8 +102,7 @@ public virtual PluginDescriptor GetPluginDescriptorByAssembly(Assembly assembly, /// >Plugin descriptor public virtual PluginDescriptor GetPluginDescriptorBySystemName(string systemName, bool installedOnly = true) { - PluginDescriptor descriptor; - if (systemName.HasValue() && _nameMap.TryGetValue(systemName, out descriptor)) + if (systemName.HasValue() && _nameMap.TryGetValue(systemName, out var descriptor)) { if (!installedOnly || descriptor.Installed) return descriptor; @@ -118,12 +120,11 @@ public virtual PluginDescriptor GetPluginDescriptorBySystemName(string systemNam /// >Plugin descriptor public virtual PluginDescriptor GetPluginDescriptorBySystemName(string systemName, bool installedOnly = true) where T : class, IPlugin { - PluginDescriptor descriptor; - if (systemName.HasValue() && _nameMap.TryGetValue(systemName, out descriptor)) + if (systemName.HasValue() && _nameMap.TryGetValue(systemName, out var descriptor)) { if (!installedOnly || descriptor.Installed) { - if (typeof(T).IsAssignableFrom(descriptor.PluginType)) + if (typeof(T).IsAssignableFrom(descriptor.PluginClrType)) return descriptor; } } diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.Cache.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.Cache.cs index 2d4e6ae541..dff9c225b5 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.Cache.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.Cache.cs @@ -38,10 +38,14 @@ private static bool DetectAndCleanStalePlugins(IEnumerable plu .Select(x => new FileInfo(Path.Combine(_shadowCopyDir.FullName, x))) .ToArray(); - foreach (var f in staleFiles) + // If that fails, then we will try to remove it the hard way by renaming it to .delete if it doesn't delete nicely + if (staleFiles.Length > 5) { - // If that fails, then we will try to remove it the hard way by renaming it to .delete if it doesn't delete nicely - TryDeleteStaleFile(f); + staleFiles.AsParallel().ForAll(f => TryDeleteStaleFile(f)); + } + else + { + staleFiles.Each(f => TryDeleteStaleFile(f)); } return true; @@ -132,14 +136,11 @@ private static void SavePluginsAssemblies(IEnumerable plugins) foreach (var p in plugins) { - if (p.ReferencedLocalAssemblyFiles != null) - { - p.ReferencedLocalAssemblyFiles.Each(x => list.Add(x.Name)); - } + p.ReferencedLocalAssemblies.Each(x => list.Add(x.OriginalFile.Name)); - if (p.OriginalAssemblyFile != null) + if (p.Assembly.OriginalFile != null) { - list.Add(p.OriginalAssemblyFile.Name); + list.Add(p.Assembly.OriginalFile.Name); } } @@ -166,15 +167,12 @@ private static int ComputePluginsHash(IEnumerable plugins) // Add each *.dll, Description.txt, web.config to the hash foreach (var p in plugins) { - if (p.OriginalAssemblyFile != null) + if (p.Assembly.OriginalFile != null) { - hashCombiner.Add(p.OriginalAssemblyFile); + hashCombiner.Add(p.Assembly.OriginalFile); } - if (p.ReferencedLocalAssemblyFiles != null) - { - p.ReferencedLocalAssemblyFiles.Each(x => hashCombiner.Add(x)); - } + p.ReferencedLocalAssemblies.Each(x => hashCombiner.Add(x.OriginalFile)); hashCombiner.Add(Path.Combine(p.PhysicalPath, "Description.txt")); hashCombiner.Add(Path.Combine(p.PhysicalPath, "web.config")); diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs index 072b362276..c7c4d6fe4e 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs @@ -15,6 +15,8 @@ using SmartStore.Core.Packaging; using SmartStore.Utilities; using SmartStore.Utilities.Threading; +using SmartStore.Core.Data; +using System.Threading.Tasks; // Contributor: Umbraco (http://www.umbraco.com). Thanks a lot! // SEE THIS POST for full details of what this does @@ -32,7 +34,7 @@ public partial class PluginManager [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetDllDirectory(string lpPathName); - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + private static readonly object _lock = new object(); private static readonly string _pluginsPath = "~/Plugins"; private static DirectoryInfo _shadowCopyDir; private static readonly ConcurrentDictionary _referencedPlugins = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); @@ -104,101 +106,122 @@ public static void Initialize() // adding a process-specific environment path (either bin/x86 or bin/x64) // ensures that unmanaged native dependencies can be resolved successfully. SetNativeDllPath(); - //SetPrivateEnvPath(); DynamicModuleUtility.RegisterModule(typeof(AutofacRequestLifetimeHttpModule)); - #region Plugins + var incompatiblePlugins = (new HashSet(StringComparer.OrdinalIgnoreCase)).AsSynchronized(); + var inactiveAssemblies = _inactiveAssemblies.AsSynchronized(); + var dirty = false; - var incompatiblePlugins = new HashSet(StringComparer.OrdinalIgnoreCase); + var watch = Stopwatch.StartNew(); - using (Locker.GetWriteLock()) - { - _shadowCopyDir = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory); + _shadowCopyDir = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory); + + var plugins = LoadPluginDescriptors().ToArray(); + var compatiblePlugins = plugins.Where(x => !x.Incompatible).ToArray(); + + var ms = watch.ElapsedMilliseconds; + Debug.WriteLine("INIT PLUGINS (LoadPluginDescriptors). Time elapsed: {0} ms.".FormatCurrentUI(ms)); + + // If plugins state is dirty, we copy files over to the dynamic folder, + // otherwise we just reference the previously copied file. + dirty = DetectAndCleanStalePlugins(compatiblePlugins); + + // Perf: Initialize/probe all plugins in parallel + plugins.AsParallel().ForAll(x => + { + // Deploy to ASP.NET dynamic folder. + DeployPlugin(x, dirty); + + // Finalize + FinalizePlugin(x); + }); + + //// Retry when failed, because during parallel execution assembly loading MAY fail. + //// Therefore we retry initialization for failed plugins, but sequentially this time. + //foreach (var p in plugins) + //{ + // // INFO: this seems redundant, but it's ok: + // // DeployPlugin() only probes assemblies that are not loaded yet. + // DeployPlugin(p, dirty); + + // // Finalize + // FinalizePlugin(p); + //} + + if (dirty && DataSettings.DatabaseIsInstalled()) + { + // Save current hash of all deployed plugins to disk + var hash = ComputePluginsHash(_referencedPlugins.Values.OrderBy(x => x.FolderName).ToArray()); + SavePluginsHash(hash); - var plugins = LoadPluginDescriptors().ToArray(); - var compatiblePlugins = plugins.Where(x => !x.Incompatible).ToArray(); + // Save names of all deployed assemblies to disk (so we can nuke them later) + SavePluginsAssemblies(_referencedPlugins.Values); + } - // If plugins state is dirty, we copy files over to the dynamic folder, - // otherwise we just reference the previously copied file. - var dirty = DetectAndCleanStalePlugins(compatiblePlugins); + IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); - foreach (var plugin in plugins) + ms = watch.ElapsedMilliseconds; + Debug.WriteLine("INIT PLUGINS (Deployment complete). Time elapsed: {0} ms.".FormatCurrentUI(ms)); + + void DeployPlugin(PluginDescriptor p, bool shadowCopy) + { + if (p.Incompatible) { - if (plugin.Incompatible) - { - incompatiblePlugins.Add(plugin.SystemName); - continue; - } - else + // Do nothing if plugin is incompatible + return; + } + + // First copy referenced local assemblies (if any) + for (int i = 0; i < p.ReferencedLocalAssemblies.Length; ++i) + { + var refAr = p.ReferencedLocalAssemblies[i]; + if (refAr.Assembly == null) { - _referencedPlugins[plugin.SystemName] = plugin; + Probe(refAr, p, shadowCopy); } + } - try - { - // Shadow copy main plugin assembly - plugin.ReferencedAssembly = Probe(plugin.OriginalAssemblyFile, dirty); - - // Shadow copy other referenced plugin local assemblies - if (plugin.ReferencedLocalAssemblyFiles != null) - { - foreach (var assemblyFile in plugin.ReferencedLocalAssemblyFiles) - { - Probe(assemblyFile, dirty); - } - } - - if (!plugin.Installed) - { - _inactiveAssemblies.Add(plugin.ReferencedAssembly); - } - - // Initialize: Find IPlugin, IPreApplicationStart, IConfigurable etc. - ActivatePlugin(plugin); - } - catch (UnauthorizedAccessException) + // Then copy main plugin assembly + var ar = p.Assembly; + if (ar.Assembly == null) + { + Probe(ar, p, shadowCopy); + if (ar.Assembly != null) { - // Throw the exception if its UnauthorizedAccessException as this will - // be because we most likely cannot copy to the dynamic folder. - throw; + // Activate (even if uninstalled): Find IPlugin, IPreApplicationStart, IConfigurable etc. + ActivatePlugin(p); } - catch (ReflectionTypeLoadException ex) - { - var msg = string.Empty; - foreach (var e in ex.LoaderExceptions) - { - msg += e.Message + Environment.NewLine; - } + } + } - HandlePluginActivationException(ex, plugin, msg, incompatiblePlugins); - } - catch (Exception ex) - { - var msg = string.Empty; - for (var e = ex; e != null; e = e.InnerException) - { - msg += e.Message + Environment.NewLine; - } + void FinalizePlugin(PluginDescriptor p) + { + _referencedPlugins[p.SystemName] = p; - HandlePluginActivationException(ex, plugin, msg, incompatiblePlugins); - } + if (p.Incompatible) + { + incompatiblePlugins.Add(p.SystemName); + return; } - if (dirty) + var firstFailedAssembly = p.ReferencedLocalAssemblies.FirstOrDefault(x => x.ActivationException != null); + if (firstFailedAssembly == null && p.Assembly.ActivationException != null) { - // Save current hash of all deployed plugins to disk - var hash = ComputePluginsHash(_referencedPlugins.Values.OrderBy(x => x.FolderName).ToArray()); - SavePluginsHash(hash); - - // Save names of all deployed assemblies to disk (so we can nuke them later) - SavePluginsAssemblies(_referencedPlugins.Values); + firstFailedAssembly = p.Assembly; + } + if (firstFailedAssembly != null) + { + Debug.WriteLine(firstFailedAssembly.ActivationException.Message); + p.Incompatible = true; + incompatiblePlugins.Add(p.SystemName); } - IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); + if ((!p.Installed || firstFailedAssembly != null) && p.Assembly.Assembly != null) + { + inactiveAssemblies.Add(p.Assembly.Assembly); + } } - - #endregion } /// @@ -215,7 +238,7 @@ private static IEnumerable LoadPluginDescriptors() if (!pluginsDir.Exists) { pluginsDir.Create(); - yield break; + return Enumerable.Empty(); } // Determine all plugin folders: ~/Plugins/{SystemName} @@ -224,19 +247,56 @@ private static IEnumerable LoadPluginDescriptors() .OrderBy(x => x.Name) .ToArray(); - var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile(); + var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile().AsSynchronized(); // Load/activate all plugins - foreach (var d in allPluginDirs) - { - var descriptor = LoadPluginDescriptor(d, installedPluginSystemNames); - if (descriptor != null) - { - yield return descriptor; - } - } + return allPluginDirs + .AsParallel() + .AsOrdered() + .Select(d => LoadPluginDescriptor(d, installedPluginSystemNames)) + .Where(x => x != null); } + ///// + ///// Loads and parses the descriptors of all installed plugins + ///// + ///// All descriptors + //private static Task ReadPluginDescriptors(BlockingCollection bag) + //{ + // // TODO: Add verbose exception handling / raising here since this is happening on app startup and could + // // prevent app from starting altogether + + // return Task.Factory.StartNew(() => + // { + // var pluginsDir = new DirectoryInfo(CommonHelper.MapPath(_pluginsPath)); + + // if (!pluginsDir.Exists) + // { + // pluginsDir.Create(); + // } + // else + // { + // // Determine all plugin folders: ~/Plugins/{SystemName} + // var allPluginDirs = pluginsDir.EnumerateDirectories().ToArray() + // .Where(x => !x.Name.IsMatch("bin") && !x.Name.IsMatch("_Backup")) + // .OrderBy(x => x.Name) + // .ToArray(); + + // var installedPluginSystemNames = PluginFileParser.ParseInstalledPluginsFile().AsSynchronized(); + + // // Load/activate all plugins + // allPluginDirs + // .AsParallel() + // .AsOrdered() + // .Select(d => LoadPluginDescriptor(d, installedPluginSystemNames)) + // .Where(x => x != null) + // .ForAll(x => bag.Add(x)); + // } + + // bag.CompleteAdding(); + // }); + //} + private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollection installedPluginSystemNames) { var descriptionFile = new FileInfo(Path.Combine(d.FullName, "Description.txt")); @@ -259,6 +319,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); @@ -279,9 +341,9 @@ private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollectio .ToDictionarySafe(x => x.Name, StringComparer.OrdinalIgnoreCase); // Set 'OriginalAssemblyFile' property - descriptor.OriginalAssemblyFile = pluginBinaries.Get(descriptor.PluginFileName); + descriptor.Assembly.OriginalFile = pluginBinaries.Get(descriptor.PluginFileName); - if (descriptor.OriginalAssemblyFile == null) + if (descriptor.Assembly.OriginalFile == null) { throw new SmartException("The main assembly '{0}' for plugin '{1}' could not be found.".FormatInvariant(descriptor.PluginFileName, descriptor.SystemName)); } @@ -291,7 +353,7 @@ private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollectio .Where(x => !x.Key.IsCaseInsensitiveEqual(descriptor.PluginFileName)) .Select(x => x.Value); - descriptor.ReferencedLocalAssemblyFiles = otherAssemblyFiles.ToArray(); + descriptor.ReferencedLocalAssemblies = otherAssemblyFiles.Select(x => new AssemblyReference { OriginalFile = x }).ToArray(); return descriptor; } @@ -299,15 +361,15 @@ private static PluginDescriptor LoadPluginDescriptor(DirectoryInfo d, ICollectio private static void ActivatePlugin(PluginDescriptor plugin) { // Init plugin type (only one plugin per assembly is allowed) - var exportedTypes = plugin.ReferencedAssembly.ExportedTypes; bool pluginFound = false; bool preStarterFound = !plugin.Installed; + var exportedTypes = plugin.Assembly.Assembly.GetExportedTypes(); foreach (var t in exportedTypes) { if (typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && t.IsClass && !t.IsAbstract) { - plugin.PluginType = t; + plugin.PluginClrType = t; plugin.IsConfigurable = typeof(IConfigurable).IsAssignableFrom(t); pluginFound = true; } @@ -329,22 +391,6 @@ private static void ActivatePlugin(PluginDescriptor plugin) } } - private static void HandlePluginActivationException(Exception ex, PluginDescriptor plugin, string msg, ICollection incompatiblePlugins) - { - msg = "Error loading plugin '{0}'".FormatInvariant(plugin.SystemName) + Environment.NewLine + msg; - - var fail = new SmartException(msg, ex); - Debug.WriteLine(fail.Message); - - if (plugin.ReferencedAssembly != null) - { - _inactiveAssemblies.Add(plugin.ReferencedAssembly); - } - - plugin.ActivationException = fail; - incompatiblePlugins.Add(plugin.SystemName); - } - /// /// Mark plugin as installed /// @@ -524,51 +570,101 @@ private static void SetNativeDllPath() /// /// Perform file deploy /// - /// Plugin main dll file info + /// Assembly reference to probe /// Reference to the shadow copied Assembly - private static Assembly Probe(FileInfo plugin, bool performShadowCopy) + private static Assembly Probe(AssemblyReference ar, PluginDescriptor d, bool shadowCopy) { - if (plugin.Directory == null || plugin.Directory.Parent == null) + var file = ar.OriginalFile; + + try { - throw new InvalidOperationException("The plugin directory for the " + plugin.Name + - " file exists in a folder outside of the allowed SmartStore folder hierarchy"); + if (file.Directory == null || file.Directory.Parent == null) + { + throw new InvalidOperationException("The plugin directory for the " + file.Name + + " file exists in a folder outside of the allowed SmartStore folder hierarchy"); + } + + ar.File = InitializeFullTrust(file, shadowCopy); + + // Load assembly locked, because concurrent load calls - even with different assemblies - + // will result in strange app init behaviour. + lock (_lock) + { + // We can now register the plugin definition + ar.Assembly = Assembly.Load(AssemblyName.GetAssemblyName(ar.File.FullName)); + + // Add the reference to the build manager + if (ar.Assembly != null) + { + // Loading assembly can fail in parallel loops. + // In this case, we'll probe again later in a sequential loop. + BuildManager.AddReferencedAssembly(ar.Assembly); + } + } + + ar.ActivationException = null; } + catch (UnauthorizedAccessException) + { + // Throw the exception if its UnauthorizedAccessException as this will + // be because we most likely cannot copy to the dynamic folder. + throw; + } + catch (ReflectionTypeLoadException ex) + { + var msg = string.Empty; + foreach (var e in ex.LoaderExceptions) + { + msg += e.Message + Environment.NewLine; + } - var probedPlugin = InitializeFullTrust(plugin, performShadowCopy); + ar.ActivationException = CreateException(msg, ex); + } + catch (Exception ex) + { + var msg = string.Empty; + for (var e = ex; e != null; e = e.InnerException) + { + msg += e.Message + Environment.NewLine; + } - // We can now register the plugin definition - var probedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(probedPlugin.FullName)); + ar.ActivationException = CreateException(msg, ex); + } - // Add the reference to the build manager - BuildManager.AddReferencedAssembly(probedAssembly); + return ar.Assembly; - return probedAssembly; - } + Exception CreateException(string message, Exception innerException) + { + return new SmartException( + "Error loading plugin '{0}'".FormatInvariant(d.SystemName) + Environment.NewLine + message, + innerException); + } + } /// /// Used to initialize plugins when running in Full Trust /// - /// Plugin main dll file + /// Plugin dll file /// Shadow copied file - private static FileInfo InitializeFullTrust(FileInfo plugin, bool performShadowCopy) + private static FileInfo InitializeFullTrust(FileInfo dll, bool shadowCopy) { - var probedPlugin = new FileInfo(Path.Combine(_shadowCopyDir.FullName, plugin.Name)); + var probedDll = new FileInfo(Path.Combine(_shadowCopyDir.FullName, dll.Name)); // If instructed to not perform the copy, just return the path to where it is supposed to be - if (!performShadowCopy && probedPlugin.Exists) - return probedPlugin; + if (!shadowCopy && probedDll.Exists) + return probedDll; try { - File.Copy(plugin.FullName, probedPlugin.FullName, true); + File.Copy(dll.FullName, probedDll.FullName, true); } catch (UnauthorizedAccessException) { - throw new UnauthorizedAccessException(string.Format("Access to the path '{0}' is denied, ensure that read, write and modify permissions are allowed.", probedPlugin.Directory.FullName)); + throw new UnauthorizedAccessException(string.Format("Access to the path '{0}' is denied, ensure that read, write and modify permissions are allowed.", probedDll.Directory.FullName)); } catch (IOException) { - Debug.WriteLine(probedPlugin.FullName + " is locked, attempting to rename"); + Debug.WriteLine(probedDll.FullName + " is locked, attempting to rename"); // This occurs when the files are locked, // For some reason devenv locks plugin files some times and for another crazy reason you are allowed to rename them @@ -576,23 +672,23 @@ private static FileInfo InitializeFullTrust(FileInfo plugin, bool performShadowC try { // If all else fails during the cleanup and we cannot copy over so we need to rename with a GUID - var deleteName = GetNewDeleteName(probedPlugin); - File.Move(probedPlugin.FullName, deleteName); + var deleteName = GetNewDeleteName(probedDll); + File.Move(probedDll.FullName, deleteName); } catch (UnauthorizedAccessException) { - throw new UnauthorizedAccessException(string.Format("Access to the path '{0}' is denied, ensure that read, write and modify permissions are allowed.", probedPlugin.Directory.FullName)); + throw new UnauthorizedAccessException(string.Format("Access to the path '{0}' is denied, ensure that read, write and modify permissions are allowed.", probedDll.Directory.FullName)); } catch (IOException exc) { - throw new IOException(probedPlugin.FullName + " rename failed, cannot initialize plugin", exc); + throw new IOException(probedDll.FullName + " rename failed, cannot initialize plugin", exc); } // OK, we've made it this so far, now retry the shadow copy - File.Copy(plugin.FullName, probedPlugin.FullName, true); + File.Copy(dll.FullName, probedDll.FullName, true); } - return probedPlugin; + return probedDll; } /// 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/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index d07a628170..8b806c43fc 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -1,13 +1,41 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Utilities; namespace SmartStore.Core.Plugins { - public class ProviderMetadata + public interface IProviderMetadata + { + /// + /// Gets the provider system name + /// + string SystemName { get; } + + /// + /// Gets the resource key pattern for user data (e.g. FriendlyName) + /// + /// + /// Plugins.{1}.{0} > 0 = provider system name, 1 = propertyname + /// + string ResourceKeyPattern { get; } + + /// + /// Gets the provider friendly name + /// + string FriendlyName { get; } + + /// + /// Gets the provider description + /// + string Description { get; } + + /// + /// Gets the provider display order + /// + int DisplayOrder { get; } + } + + public class ProviderMetadata : IProviderMetadata { /// /// Gets or sets the provider type @@ -112,7 +140,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return SystemName.GetHashCode(); + return HashCodeCombiner.Start().Add(typeof(ProviderMetadata)).Add(SystemName).CombinedHash; } } } diff --git a/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs b/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs index a9c344cf7d..e0df140361 100644 --- a/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs +++ b/src/Libraries/SmartStore.Core/Search/AcquireWriterContext.cs @@ -1,15 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Search { public enum AcquirementReason { Indexing, - Optimizing, Deleting } @@ -20,13 +21,13 @@ public AcquireWriterContext(AcquirementReason reason) Reason = reason; Languages = new List(); Currencies = new List(); - StoreIds = new int[0]; StoreMappings = new Multimap(); CustomerRoleIds = new int[0]; CustomerRoleMappings = new Multimap(); DeliveryTimes = new Dictionary(); Manufacturers = new Dictionary(); Categories = new Dictionary(); + Translations = new Dictionary(StringComparer.OrdinalIgnoreCase); CustomProperties = new Dictionary(); } @@ -45,15 +46,15 @@ public AcquireWriterContext(AcquirementReason reason) /// public IList Currencies { get; set; } - /// - /// Array of all store identifiers - /// - public int[] StoreIds { get; set; } + /// + /// All stores + /// + public IList Stores { get; set; } - /// - /// Map of product to store identifiers if the product is limited to certain stores - /// - public Multimap StoreMappings { get; set; } + /// + /// Map of product to store identifiers if the product is limited to certain stores + /// + public Multimap StoreMappings { get; set; } /// /// Array of all customer role identifiers @@ -65,10 +66,10 @@ public AcquireWriterContext(AcquirementReason reason) /// public Multimap CustomerRoleMappings { get; set; } - /// - /// All manufacturers - /// - public Dictionary Manufacturers { get; set; } + /// + /// All manufacturers + /// + public Dictionary Manufacturers { get; set; } /// /// All categories @@ -80,9 +81,27 @@ public AcquireWriterContext(AcquirementReason reason) /// public Dictionary DeliveryTimes { get; set; } + /// + /// All translations for global scopes (like Category, Manufacturer etc.) + /// + public Dictionary Translations { get; set; } + /// /// Use this dictionary for any custom data required along indexing /// public Dictionary CustomProperties { get; set; } + + public void Clear() + { + Languages.Clear(); + Currencies.Clear(); + StoreMappings.Clear(); + CustomerRoleMappings.Clear(); + Manufacturers.Clear(); + Categories.Clear(); + DeliveryTimes.Clear(); + Translations.Clear(); + CustomProperties.Clear(); + } } } 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..80e54b44af 100644 --- a/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs +++ b/src/Libraries/SmartStore.Core/Search/Facets/FacetValue.cs @@ -100,8 +100,8 @@ public override int GetHashCode() { var combiner = HashCodeCombiner .Start() - .Add(Value.GetHashCode()) - .Add(UpperValue.GetHashCode()); + .Add(Value) + .Add(UpperValue); return combiner.CombinedHash; } @@ -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/IIndexStore.cs b/src/Libraries/SmartStore.Core/Search/IIndexStore.cs index c62c96b039..36e7fe6a23 100644 --- a/src/Libraries/SmartStore.Core/Search/IIndexStore.cs +++ b/src/Libraries/SmartStore.Core/Search/IIndexStore.cs @@ -15,11 +15,6 @@ public interface IIndexStore /// void Delete(); - /// - /// Optimizes the index - /// - void Optimize(); - /// /// Returns the name of the index /// @@ -30,11 +25,21 @@ public interface IIndexStore /// bool Exists { get; } - /// - /// Gets the total number of indexed documents - /// - /// Type of document, use null to get all documents - int GetDocumentCount(SearchDocumentType? documentType); + /// + /// Gets the size of the index in bytes. + /// + long IndexSize { get; } + + /// + /// The identifier of the last added document. + /// + int LastAddedDocumentId { get; } + + /// + /// Gets the total number of indexed documents + /// + /// Type of document, use null to get all documents + int GetDocumentCount(SearchDocumentType? documentType); /// /// Returns every field's name available in the index diff --git a/src/Libraries/SmartStore.Core/Search/ISearchBits.cs b/src/Libraries/SmartStore.Core/Search/ISearchBits.cs deleted file mode 100644 index c9ba405ef8..0000000000 --- a/src/Libraries/SmartStore.Core/Search/ISearchBits.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace SmartStore.Core.Search -{ - public interface ISearchBits - { - ISearchBits And(ISearchBits other); - ISearchBits Or(ISearchBits other); - ISearchBits Xor(ISearchBits other); - long Count(); - } -} diff --git a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs index 70e9dcb508..12235f2d44 100644 --- a/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs +++ b/src/Libraries/SmartStore.Core/Search/ISearchEngine.cs @@ -17,12 +17,6 @@ public interface ISearchEngine /// Search hit ISearchHit Get(int id); - /// - /// Get search bits - /// - /// Search bits - ISearchBits GetBits(); - /// /// Get total number of search hits /// @@ -47,13 +41,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..7389b7a5cf --- /dev/null +++ b/src/Libraries/SmartStore.Core/Search/IndexInfo.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using SmartStore.Utilities; + +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 long IndexSize { get; set; } + public int LastAddedDocumentId { 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("index-size", IndexSize), + new XElement("last-added-document-id", LastAddedDocumentId), + 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 indexSize = doc.Descendants("index-size").FirstOrDefault()?.Value; + if (indexSize.HasValue() && CommonHelper.TryConvert(indexSize, out long size)) + { + info.IndexSize = size; + } + + var lastAddedDocumentId = doc.Descendants("last-added-document-id").FirstOrDefault()?.Value; + if (lastAddedDocumentId.HasValue()) + { + info.LastAddedDocumentId = lastAddedDocumentId.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/SmartException.cs b/src/Libraries/SmartStore.Core/SmartException.cs index 67609e40d1..0b620fa609 100644 --- a/src/Libraries/SmartStore.Core/SmartException.cs +++ b/src/Libraries/SmartStore.Core/SmartException.cs @@ -40,8 +40,7 @@ public SmartException(string messageFormat, params object[] args) /// /// The SerializationInfo that holds the serialized object data about the exception being thrown. /// The StreamingContext that contains contextual information about the source or destination. - protected SmartException(SerializationInfo - info, StreamingContext context) + protected SmartException(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index fc127015fc..e89e76c15d 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -63,11 +64,10 @@ - ..\..\packages\AngleSharp.0.9.9\lib\net45\AngleSharp.dll + ..\..\packages\AngleSharp.0.9.11\lib\net45\AngleSharp.dll - - ..\..\packages\Autofac.4.5.0\lib\net45\Autofac.dll - True + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll @@ -81,6 +81,9 @@ ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll True + + ..\..\packages\HtmlSanitizer.4.0.205\lib\net45\HtmlSanitizer.dll + ..\..\packages\log4net.2.0.8\lib\net45-full\log4net.dll True @@ -89,13 +92,11 @@ 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 - True + + ..\..\packages\Microsoft.Web.Xdt.3.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll @@ -119,33 +120,27 @@ - - ..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + + ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll + + ..\..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - False - ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll @@ -160,10 +155,12 @@ Properties\AssemblyVersionInfo.cs - + + + @@ -179,9 +176,12 @@ + + + @@ -198,7 +198,6 @@ - @@ -209,13 +208,22 @@ + + + + + + + + + @@ -228,6 +236,10 @@ + + + + @@ -237,16 +249,29 @@ + + + + + + + + + + + + - + + @@ -255,6 +280,9 @@ + + + @@ -266,7 +294,9 @@ - + + + @@ -274,6 +304,7 @@ + @@ -318,11 +349,7 @@ - - - - @@ -374,7 +401,6 @@ - @@ -430,6 +456,7 @@ + @@ -441,13 +468,15 @@ + + + - @@ -457,7 +486,8 @@ - + + @@ -506,7 +536,6 @@ - @@ -517,7 +546,6 @@ - @@ -664,6 +692,7 @@ + @@ -672,6 +701,7 @@ + diff --git a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs index 47c338ac27..e5bc5e5e48 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,13 +36,18 @@ 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"), + new Version("3.2"), + new Version("3.2.1"), + new Version("3.2.2") + }; private const string HELP_BASEURL = "https://docs.smartstore.com/display/"; @@ -68,8 +93,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 +115,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") + ? "SDDE32" + : "SMNET32"; } /// diff --git a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs index 6e3c043b89..0c90853524 100644 --- a/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs +++ b/src/Libraries/SmartStore.Core/Themes/DefaultThemeRegistry.cs @@ -8,6 +8,7 @@ using System.Threading; using SmartStore.Collections; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.Core.Logging; using SmartStore.Utilities; @@ -29,10 +30,10 @@ public partial class DefaultThemeRegistry : DisposableObject, IThemeRegistry public DefaultThemeRegistry(IEventPublisher eventPublisher, IApplicationEnvironment env, bool? enableMonitoring, string themesBasePath, bool autoLoadThemes) { - this._enableMonitoring = enableMonitoring ?? CommonHelper.GetAppSetting("sm:MonitorThemesFolder", true); - this._eventPublisher = eventPublisher; - this._env = env; - this._themesBasePath = themesBasePath.NullEmpty() ?? _env.ThemesFolder.RootPath; + _enableMonitoring = enableMonitoring ?? CommonHelper.GetAppSetting("sm:MonitorThemesFolder", true); + _eventPublisher = eventPublisher; + _env = env; + _themesBasePath = themesBasePath.NullEmpty() ?? _env.ThemesFolder.RootPath; Logger = NullLogger.Instance; @@ -435,7 +436,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/InheritedThemeFileResult.cs b/src/Libraries/SmartStore.Core/Themes/InheritedThemeFileResult.cs index 09c40503e8..77d708c5c8 100644 --- a/src/Libraries/SmartStore.Core/Themes/InheritedThemeFileResult.cs +++ b/src/Libraries/SmartStore.Core/Themes/InheritedThemeFileResult.cs @@ -34,10 +34,10 @@ public class InheritedThemeFileResult /// public string ResultThemeName { get; set; } - public bool IsExplicit { get; set; } + public bool IsBased { get; set; } /// - /// The query string, e.g. '?explicit' + /// The query string, e.g. '?base' /// public string Query { get; set; } } 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..1fb1f2240d 100644 --- a/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/CommonHelper.cs @@ -10,13 +10,29 @@ 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 { public static partial class CommonHelper { private static bool? _isDevEnvironment; + + [ThreadStatic] + private static Random _random; + private static Random GetRandomizer() + { + if (_random == null) + { + _random = new Random(); + } + + return _random; + } + /// /// Generate random digit code /// @@ -24,19 +40,21 @@ public static partial class CommonHelper /// Result string public static string GenerateRandomDigitCode(int length) { - var random = new Random(); - string str = string.Empty; - for (int i = 0; i < length; i++) - str = String.Concat(str, random.Next(10).ToString()); - return str; - } + var buffer = new int[length]; + for (int i = 0; i < length; ++i) + { + buffer[i] = GetRandomizer().Next(10); + } + + return string.Join("", buffer); + } /// - /// Returns an random interger number within a specified rage + /// Returns a random number within the range to - 1. /// /// Minimum number - /// Maximum number - /// Result + /// Maximum number (exclusive!). + /// Random integer number. public static int GenerateRandomInteger(int min = 0, int max = 2147483647) { var randomNumberBuffer = new byte[10]; @@ -56,7 +74,8 @@ public static int GenerateRandomInteger(int min = 0, int max = 2147483647) /// public static string MapPath(string path, bool findAppRoot = true) { - Guard.NotNull(path, nameof(path)); + if (path == null) + throw new ArgumentNullException(nameof(path)); if (HostingEnvironment.IsHosted) { @@ -178,7 +197,8 @@ public static ExpandoObject ToExpando(object value) public static IDictionary ObjectToDictionary(object obj) { - Guard.NotNull(obj, nameof(obj)); + if (obj == null) + throw new ArgumentNullException(nameof(obj)); return FastProperty.ObjectToDictionary( obj, @@ -265,5 +285,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 c4c458a569..40572c1b8b 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs @@ -104,10 +104,10 @@ public FileDownloadResponse DownloadFile(string url, bool sendAuthCookie = false /// Items to be downloaded public async Task DownloadAsync(FileDownloadManagerContext context, IEnumerable items) { - await DownloadFiles(context, items); + await DownloadFilesAsync(context, items); } - private async Task DownloadFiles(FileDownloadManagerContext context, IEnumerable items) + private async Task DownloadFilesAsync(FileDownloadManagerContext context, IEnumerable items) { try { @@ -139,10 +139,12 @@ from item in items } } } - catch (Exception exception) + catch (Exception ex) { - if (context.Logger != null) - context.Logger.ErrorsAll(exception); + if (context.Logger != null) + { + context.Logger.ErrorsAll(ex); + } } } @@ -156,53 +158,65 @@ private async Task ProcessUrl(FileDownloadManagerContext context, HttpClient cli using (var response = await client.GetAsync(item.Url)) { - if (response.IsSuccessStatusCode && response.Content.Headers.ContentType != null) - { - 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"; - - item.MimeType = contentType; - item.Path = Path.ChangeExtension(item.Path, extension.EnsureStartsWith(".")); - } - } - - //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); - } + if (response.IsSuccessStatusCode) + { + if (response.Content.Headers.ContentType != null) + { + 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"; + + item.MimeType = contentType; + item.Path = Path.ChangeExtension(item.Path, extension.EnsureStartsWith(".")); + } + } + + //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; + } + else + { + item.Success = false; + item.ErrorMessage = response.StatusCode.ToString(); + } + } } - catch (Exception exception) + catch (Exception ex) { try { item.Success = false; - item.ErrorMessage = exception.ToAllMessages(); - - var webExc = exception.InnerException as WebException; - if (webExc != null) - item.ExceptionStatus = webExc.Status; - - if (context.Logger != null) - context.Logger.Error(exception, item.ToString()); + item.ErrorMessage = ex.ToAllMessages(); + + var webExc = ex.InnerException as WebException; + if (webExc != null) + { + item.ExceptionStatus = webExc.Status; + } + + if (context.Logger != null) + { + context.Logger.Error(ex, item.ToString()); + } } catch { } } diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index 7149375996..2ce13789c5 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -7,15 +7,18 @@ namespace SmartStore.Utilities { - public static class FileSystemHelper + public static class FileSystemHelper { + private static readonly string _pathTemp = CommonHelper.MapPath(CommonHelper.GetAppSetting("sm:TempDirectory", "~/App_Data/_temp")); + private static readonly string _pathTempTenant = CommonHelper.MapPath(DataSettings.Current.TenantPath + "/_temp"); + /// /// Returns physical path to application temp directory /// /// Name of a sub directory to be created and returned (optional) public static string TempDir(string subDirectory = null) { - return TempDirInternal(CommonHelper.GetAppSetting("sm:TempDirectory", "~/App_Data/_temp"), subDirectory); + return TempDirInternal(_pathTemp, subDirectory); } /// @@ -24,18 +27,16 @@ public static string TempDir(string subDirectory = null) /// Name of a sub directory to be created and returned (optional) public static string TempDirTenant(string subDirectory = null) { - return TempDirInternal(DataSettings.Current.TenantPath + "/_temp", subDirectory); + return TempDirInternal(_pathTempTenant, subDirectory); } - private static string TempDirInternal(string virtualPath, string subDirectory = null) + private static string TempDirInternal(string path, string subDirectory = null) { - var path = CommonHelper.MapPath(virtualPath); - if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } - + if (subDirectory.HasValue()) { path = Path.Combine(path, subDirectory); @@ -49,91 +50,157 @@ private static string TempDirInternal(string virtualPath, string subDirectory = return path; } + /// + /// Safe way to cleanup temporary directories. Should be called via scheduled task. + /// + public static void ClearTempDirectories() + { + var olderThan = TimeSpan.FromHours(5); + + ClearDirectory(new DirectoryInfo(TempDir()), false, olderThan); + ClearDirectory(new DirectoryInfo(TempDirTenant()), false, olderThan); + } + /// - /// Ensures that path is a valid root path + /// Safe way to delete all directory content /// - /// Relative path - /// Valid root path - public static string ValidateRootPath(string path) + /// Directory path + /// Delete dir too if it doesn't contain any entries after deletion anymore + /// Delete only files older than this TimeSpan + /// Name of files not to be deleted + public static void ClearDirectory(string path, bool deleteIfEmpfy, TimeSpan olderThan, params string[] exceptFileNames) { - if (path.HasValue()) - { - path = path.Replace('\\', '/'); + if (path.IsEmpty()) + return; - if (!path.StartsWith("~/")) - { - if (path.StartsWith("~")) - path = path.Substring(1); + ClearDirectory(new DirectoryInfo(path), deleteIfEmpfy, olderThan, exceptFileNames); + } - path = (path.StartsWith("/") ? "~" : "~/") + path; - } - } - return path; + /// + /// Safe way to delete all directory content + /// + /// Directory path + /// Delete dir too if it doesn't contain any entries after deletion anymore + /// Name of files not to be deleted + public static void ClearDirectory(string path, bool deleteIfEmpfy, params string[] exceptFileNames) + { + if (path.IsEmpty()) + return; + + ClearDirectory(new DirectoryInfo(path), deleteIfEmpfy, TimeSpan.Zero, exceptFileNames); } /// - /// Safe way to cleanup the temp directory. Should be called via scheduled task. + /// Safe way to delete all directory content /// - public static void TempCleanup() + /// Directory info object + /// Delete dir too if it doesn't contain any entries after deletion anymore + /// Name of files not to be deleted + public static void ClearDirectory(DirectoryInfo dir, bool deleteIfEmpfy, params string[] exceptFileNames) { - try - { - var dirs = new string[] { TempDir(), TempDirTenant() }; + Guard.NotNull(dir, nameof(dir)); - foreach (var dir in dirs) + ClearDirectory(dir, deleteIfEmpfy, TimeSpan.Zero, exceptFileNames); + } + + /// + /// Safe way to delete all directory content + /// + /// Directory info object + /// Delete dir too if it doesn't contain any entries after deletion anymore + /// Delete only files older than this TimeSpan + /// Name of files not to be deleted + public static void ClearDirectory(DirectoryInfo dir, bool deleteIfEmpfy, TimeSpan olderThan, params string[] exceptFileNames) + { + Guard.NotNull(dir, nameof(dir)); + + if (!dir.Exists) + return; + + var olderThanDate = DateTime.UtcNow.Subtract(olderThan); + + for (int i = 0; i < 10; i++) + { + try { - if (Directory.Exists(dir)) + foreach (var fsi in dir.EnumerateFileSystemInfos()) { - var oldestDate = DateTime.Now.Subtract(new TimeSpan(0, 5, 0, 0)); - var files = Directory.EnumerateFiles(dir); - - foreach (string file in files) + if (fsi is FileInfo file) { - var fi = new FileInfo(file); + if (file.LastWriteTimeUtc >= olderThanDate) + continue; + + if (exceptFileNames.Any(x => x.IsCaseInsensitiveEqual(file.Name))) + continue; - if (fi.LastWriteTime < oldestDate) - Delete(file); + if (file.IsReadOnly) + { + file.IsReadOnly = false; + } + + file.Delete(); + } + else if (fsi is DirectoryInfo subDir) + { + ClearDirectory(subDir, true, olderThan, exceptFileNames); } + } + + break; + } + catch (Exception ex) + { + ex.Dump(); } } - catch (Exception ex) + + if (deleteIfEmpfy) { - ex.Dump(); + try + { + if (!dir.EnumerateFileSystemInfos().Any()) + { + dir.Delete(true); + } + } + catch (Exception ex) + { + ex.Dump(); + } } } /// /// Safe way to delete a file. /// - public static bool Delete(string path) + public static bool DeleteFile(string path) { if (path.IsEmpty()) return true; - bool result = true; try { if (Directory.Exists(path)) { - throw new MemberAccessException("Deleting folders due to security reasons not possible: {0}".FormatWith(path)); + throw new UnauthorizedAccessException("Deleting folders not possible due to security reasons: {0}".FormatWith(path)); } - File.Delete(path); // no exception, if file doesn't exists + // No exception, if file doesn't exists + File.Delete(path); + return true; } - catch (Exception exc) + catch (Exception ex) { - result = false; - exc.Dump(); + ex.Dump(); + return false; } - - return result; } /// /// Safe way to copy a file. /// - public static bool Copy(string sourcePath, string destinationPath, bool overwrite = true, bool deleteSource = false) + public static bool CopyFile(string sourcePath, string destinationPath, bool overwrite = true, bool deleteSource = false) { bool result = true; try @@ -141,13 +208,14 @@ public static bool Copy(string sourcePath, string destinationPath, bool overwrit File.Copy(sourcePath, destinationPath, overwrite); if (deleteSource) - Delete(sourcePath); + DeleteFile(sourcePath); } catch (Exception exc) { result = false; exc.Dump(); } + return result; } @@ -173,10 +241,10 @@ public static bool CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo { fi.CopyTo(Path.Combine(target.ToString(), fi.Name), overwrite); } - catch (Exception exc) + catch (Exception ex) { result = false; - exc.Dump(); + ex.Dump(); } } @@ -187,83 +255,16 @@ public static bool CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo DirectoryInfo targetSubDir = target.CreateSubdirectory(sourceSubDir.Name); CopyDirectory(sourceSubDir, targetSubDir, overwrite); } - catch (Exception exc) + catch (Exception ex) { result = false; - exc.Dump(); + ex.Dump(); } } return result; } - /// - /// Safe way to delete all directory content - /// - /// A directory path - /// Delete directoryPath too - /// Name of files not to be deleted - public static void ClearDirectory(string directoryPath, bool selfToo, List exceptFileNames = null) - { - if (directoryPath.IsEmpty()) - return; - - try - { - var dir = new DirectoryInfo(directoryPath); - - foreach (var fi in dir.GetFiles()) - { - if (exceptFileNames != null && exceptFileNames.Any(x => x.IsCaseInsensitiveEqual(fi.Name))) - continue; - - try - { - fi.IsReadOnly = false; - fi.Delete(); - } - catch (Exception) - { - try - { - Thread.Sleep(0); - fi.Delete(); - } - catch (Exception) { } - } - } - - foreach (var di in dir.GetDirectories()) - { - ClearDirectory(di.FullName, false); - - try - { - di.Delete(); - } - catch (Exception) - { - try - { - Thread.Sleep(0); - di.Delete(); - } - catch (Exception) { } - } - } - } - catch (Exception) { } - - if (selfToo) - { - try - { - Directory.Delete(directoryPath, true); // just deletes the (now empty) directory - } - catch (Exception) { } - } - } - /// /// Creates a non existing directory name /// @@ -299,7 +300,7 @@ public static int CountFiles(string directoryPath) { return Directory.GetFiles(directoryPath).Length; } - catch (Exception) { } + catch { } return 0; } @@ -312,23 +313,9 @@ public static void ClearFile(string path) { try { - if (path.HasValue()) - File.WriteAllText(path, ""); + if (path.HasValue()) File.WriteAllText(path, ""); } - catch (Exception) { } - } - - /// - /// Checks whether the given path is a fully qualified absolute path (either UNC or rooted with drive letter) - /// - /// Path to check - /// true if path is fully qualified - public static bool IsFullPath(string path) - { - return !String.IsNullOrWhiteSpace(path) - && path.IndexOfAny(System.IO.Path.GetInvalidPathChars().ToArray()) == -1 - && Path.IsPathRooted(path) - && !Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal); + catch { } } } } diff --git a/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs b/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs index d0c2db8dec..585f13a9ea 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) { @@ -100,14 +105,12 @@ public HashCodeCombiner Add(FileSystemInfo fi, bool deep = true) Add(fi.CreationTimeUtc); Add(fi.LastWriteTimeUtc); - var file = fi as FileInfo; - if (file != null) + if (fi is FileInfo file) { Add(file.Length.GetHashCode()); } - var dir = fi as DirectoryInfo; - if (dir != null) + if (fi is DirectoryInfo dir) { foreach (var f in dir.GetFiles()) { diff --git a/src/Libraries/SmartStore.Core/Utilities/PathHelper.cs b/src/Libraries/SmartStore.Core/Utilities/PathHelper.cs new file mode 100644 index 0000000000..d1cacbacbf --- /dev/null +++ b/src/Libraries/SmartStore.Core/Utilities/PathHelper.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace SmartStore.Utilities +{ + public static class PathHelper + { + private static readonly char[] _invalidPathChars; + private static readonly char[] _invalidFileNameChars; + private static readonly Regex _invalidCharsPattern; + + static PathHelper() + { + _invalidPathChars = Path.GetInvalidPathChars(); + _invalidFileNameChars = Path.GetInvalidFileNameChars(); + + var invalidChars = Regex.Escape(new string(_invalidPathChars) + new string(_invalidFileNameChars)); + _invalidCharsPattern = new Regex(string.Format(@"[{0}]+", invalidChars)); + } + + /// + /// Checks whether path is empty or starts with '/' or '\' + /// + public static bool IsRootedPath(string basepath) + { + return (string.IsNullOrEmpty(basepath) || basepath[0] == '/' || basepath[0] == '\\'); + } + + /// + /// Checks whether path is starts with '~/' + /// + public static bool IsAppRelativePath(string path) + { + if (path == null) + return false; + + int len = path.Length; + + // Empty string case + if (len == 0) return false; + + // It must start with ~ + if (path[0] != '~') + return false; + + // Single character case: "~" + if (len == 1) + return true; + + // If it's longer, checks if it starts with "~/" or "~\" + return path[1] == '\\' || path[1] == '/'; + } + + /// + /// Checks if virtual path contains a protocol, which is referred to as a scheme in the + /// URI spec. + /// + public static bool HasScheme(string virtualPath) + { + // URIs have the format :, e.g. mailto:user@ms.com, + // http://server/, nettcp://server/, etc. The cannot contain slashes. + // The virtualPath passed to this method may be absolute or relative. Although + // ':' is only allowed in the if it is encoded, the + // virtual path that we're receiving here may be decoded, so it is impossible + // for us to determine if virtualPath has a scheme. We will be conservative + // and err on the side of assuming it has a scheme when we cannot tell for certain. + // To do this, we first check for ':'. If not found, then it doesn't have a scheme. + // If ':' is found, then as long as we find a '/' before the ':', it cannot be + // a scheme because schemes don't contain '/'. Otherwise, we will assume it has a + // scheme. + int indexOfColon = virtualPath.IndexOf(':'); + if (indexOfColon == -1) + return false; + int indexOfSlash = virtualPath.IndexOf('/'); + return (indexOfSlash == -1 || indexOfColon < indexOfSlash); + } + + /// + /// Ensures that a path is a valid app root path by checking whether it starts + /// with '~/' and prepending it if needed and by replacing '\' with '/' + /// + /// Relative path + /// Normalized root path + public static string NormalizeAppRelativePath(string path) + { + if (path.IsEmpty()) + return path; + + path = path.Replace('\\', '/'); + + if (!path.StartsWith("~/")) + { + if (path.StartsWith("~")) + path = path.Substring(1); + + path = (path.StartsWith("/") ? "~" : "~/") + path; + } + + return path; + } + + /// + /// Checks whether a path is a safe app root path. + /// + /// Relative path + public static bool IsSafeAppRootPath(string path) + { + if (path.EmptyNull().Length > 2 && !path.IsCaseInsensitiveEqual("con") && !HasInvalidPathChars(path)) + { + try + { + var mappedPath = CommonHelper.MapPath(path); + var appPath = CommonHelper.MapPath("~/"); + return !mappedPath.IsCaseInsensitiveEqual(appPath); + } + catch { } + } + + return false; + } + + /// + /// Replaces all occurences of any illegal path or file name char by '-' + /// + /// Path/File name + /// Sanitized path/file name + public static string SanitizeFileName(string name) + { + if (name.IsEmpty()) + return name; + + return _invalidCharsPattern.Replace(name, "-"); + } + + public static bool HasInvalidPathChars(string path, bool checkWildcardChars = false) + { + if (path == null) + return false; + + return path.IndexOfAny(_invalidPathChars) >= 0 + || (checkWildcardChars && ContainsWildcardChars(path, 0)); + } + + public static bool HasInvalidFileNameChars(string fileName, bool checkWildcardChars = false) + { + if (fileName == null) + return false; + + return fileName.IndexOfAny(_invalidFileNameChars) >= 0 + || (checkWildcardChars && ContainsWildcardChars(fileName, 0)); + } + + private static bool ContainsWildcardChars(string path, int startIndex = 0) + { + for (int i = startIndex; i < path.Length; i++) + { + switch (path[i]) + { + case '*': + case '?': + return true; + } + } + + return false; + } + + /// + /// Checks whether the given path is a fully qualified absolute path (either UNC or rooted with drive letter) + /// + /// Path to check + /// true if path is fully qualified + public static bool IsAbsolutePhysicalPath(string path) + { + if ((path == null) || (path.Length < 3)) + { + return false; + } + + return (((path[1] == ':') && IsDirectorySeparatorChar(path[2])) || IsUncSharePath(path)); + } + + internal static bool IsUncSharePath(string path) => + (((path.Length > 2) && IsDirectorySeparatorChar(path[0])) && IsDirectorySeparatorChar(path[1])); + + + private static bool IsDirectorySeparatorChar(char ch) + { + if (ch != '\\') + { + return (ch == '/'); + } + + return true; + } + } +} diff --git a/src/Libraries/SmartStore.Core/Utilities/Retry.cs b/src/Libraries/SmartStore.Core/Utilities/Retry.cs index 7c42ace8d6..916b64e3b2 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Retry.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Retry.cs @@ -86,11 +86,11 @@ public static async Task RunAsync(Func operation, int attempts, TimeSpan? { Guard.NotNull(operation, nameof(operation)); - Func> wrapper = async () => + async Task wrapper() { - await operation().ConfigureAwait(false); + await operation(); return true; - }; + } await RunAsync(wrapper, attempts, wait, onFailed); } @@ -123,7 +123,7 @@ public static async Task RunAsync(Func> operation, int attempts, T try { // Call the function passed in by the caller. - return await operation().ConfigureAwait(false); + return await operation(); } catch (Exception ex) { diff --git a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs index 6613ba70f6..721b1a38b8 100644 --- a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs @@ -1,81 +1,132 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Text; namespace SmartStore.Utilities { public static class SeoHelper { - private static readonly string _okChars = "abcdefghijklmnopqrstuvwxyz1234567890 _-/"; private static readonly object _lock = new object(); - private static Dictionary _seoCharacterTable; - private static Dictionary _userSeoCharacterTable; + private static Dictionary _userSeoCharacterTable; /// /// Get SEO friendly string /// /// 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; - - name = name.Trim().ToLowerInvariant(); - - if (charConversions != null && _userSeoCharacterTable == null) - { - InitializeUserSeoCharacterTable(charConversions); - } - - if (convertNonWesternChars && _seoCharacterTable == null) - { - InitializeSeoCharacterTable(); - } - - var sb = new StringBuilder(); - foreach (char c in name) - { - var c2 = c.ToString(); - - if (charConversions != null && _userSeoCharacterTable != null && _userSeoCharacterTable.ContainsKey(c2)) - { - sb.Append(_userSeoCharacterTable[c2]); - continue; - } - - if (convertNonWesternChars && _seoCharacterTable.ContainsKey(c2)) - { - c2 = _seoCharacterTable[c2]; - } - - if (_okChars.Contains(c2) || (allowUnicodeCharsInUrls && char.IsLetterOrDigit(c))) - { - sb.Append(c2); - } - } - - sb.Replace(" ", "-"); - - var result = sb.ToString(); - - while (result.Contains("--")) - { - result = result.Replace("--", "-"); - } - while (result.Contains("__")) - { - result = result.Replace("__", "_"); - } - return result.Trim('/'); + return GetSeName(name, convertNonWesternChars, allowUnicodeChars, true, charConversions); } - public static void ResetUserSeoCharacterTable() + /// + /// Get SEO friendly string + /// + /// 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 the forward slash (/) is allowed. Should be false for physical file names. + /// Raw data of semicolon separated char conversions + /// SEO friendly string + [SuppressMessage("ReSharper", "PossibleNullReferenceException")] + public static string GetSeName( + string name, + bool convertNonWesternChars, + bool allowUnicodeChars, + bool allowForwardSlash, + string charConversions = null) + { + // Return empty value if text is null + if (name == null) return ""; + + const int maxlen = 400; + + if (charConversions != null && _userSeoCharacterTable == null) + { + InitializeUserSeoCharacterTable(charConversions); + } + + // Normalize + name = name.ToLowerInvariant(); + + var len = name.Length; + var sb = new StringBuilder(len); + var prevdash = false; + + char c; + + for (int i = 0; i < len; i++) + { + c = name[i]; + + if (charConversions != null && _userSeoCharacterTable != null && _userSeoCharacterTable.TryGetValue(c, out string userChar)) + { + sb.Append(userChar); + prevdash = false; + } + else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '/') + { + if (c != '/' || allowForwardSlash) + { + sb.Append(c); + prevdash = (c == '_'); + } + } + else if (c == ' ' || c == ',' || c == '.' || c == '\\' || c == '-' || c == '_' || c == '=') + { + if (!prevdash && sb.Length > 0) + { + sb.Append('-'); + prevdash = true; + } + } + else + { + var category = CharUnicodeInfo.GetUnicodeCategory(c); + + if (category >= UnicodeCategory.ConnectorPunctuation && category <= UnicodeCategory.MathSymbol) + { + if (!prevdash && sb.Length > 0) + { + sb.Append('-'); + prevdash = true; + } + } + else if ((int)c >= 128) + { + var prevlen = sb.Length; + var c2 = c; + + if (convertNonWesternChars) + { + c2 = c.TryRemoveDiacritic(); + } + + if ((allowUnicodeChars && Char.IsLetterOrDigit(c2)) || (c2 >= 'a' && c2 <= 'z')) + { + sb.Append(c2); + } + + if (prevlen != sb.Length) prevdash = false; + } + } + + if (i >= maxlen) break; + } + + if (prevdash) + return sb.ToString().Substring(0, sb.Length - 1).Trim('/'); + else + return sb.ToString().Trim('/'); + } + + public static void ResetUserSeoCharacterTable() { if (_userSeoCharacterTable != null) { @@ -90,1014 +141,17 @@ private static void InitializeUserSeoCharacterTable(string charConversions) { if (_userSeoCharacterTable == null) { - _userSeoCharacterTable = new Dictionary(); - - string strLeft, strRight; + _userSeoCharacterTable = new Dictionary(); foreach (var conversion in charConversions.SplitSafe(Environment.NewLine)) { - if (conversion.SplitToPair(out strLeft, out strRight, ";") && strLeft.HasValue() && !_userSeoCharacterTable.ContainsKey(strLeft)) + if (conversion.SplitToPair(out var strLeft, out var strRight, ";") && strLeft.HasValue() && !_userSeoCharacterTable.ContainsKey(strLeft[0])) { - _userSeoCharacterTable.Add(strLeft, strRight); + _userSeoCharacterTable.Add(strLeft[0], strRight); } } } } } - - /// - /// Stores Unicode characters and their "normalized" - /// values to a hash table. Character codes are referenced - /// by hex numbers because that's the most common way to - /// refer to them. - /// - /// Upper-case comments are identifiers from the Unicode database. - /// Lower- and mixed-case comments are the author's - /// - private static void InitializeSeoCharacterTable() - { - lock (_lock) - { - if (_seoCharacterTable == null) - { - var localTable = new Dictionary(); - localTable.Add(ToUnichar("0041"), "A"); - localTable.Add(ToUnichar("0042"), "B"); - localTable.Add(ToUnichar("0043"), "C"); - localTable.Add(ToUnichar("0044"), "D"); - localTable.Add(ToUnichar("0045"), "E"); - localTable.Add(ToUnichar("0046"), "F"); - localTable.Add(ToUnichar("0047"), "G"); - localTable.Add(ToUnichar("0048"), "H"); - localTable.Add(ToUnichar("0049"), "I"); - localTable.Add(ToUnichar("004A"), "J"); - localTable.Add(ToUnichar("004B"), "K"); - localTable.Add(ToUnichar("004C"), "L"); - localTable.Add(ToUnichar("004D"), "M"); - localTable.Add(ToUnichar("004E"), "N"); - localTable.Add(ToUnichar("004F"), "O"); - localTable.Add(ToUnichar("0050"), "P"); - localTable.Add(ToUnichar("0051"), "Q"); - localTable.Add(ToUnichar("0052"), "R"); - localTable.Add(ToUnichar("0053"), "S"); - localTable.Add(ToUnichar("0054"), "T"); - localTable.Add(ToUnichar("0055"), "U"); - localTable.Add(ToUnichar("0056"), "V"); - localTable.Add(ToUnichar("0057"), "W"); - localTable.Add(ToUnichar("0058"), "X"); - localTable.Add(ToUnichar("0059"), "Y"); - localTable.Add(ToUnichar("005A"), "Z"); - localTable.Add(ToUnichar("0061"), "a"); - localTable.Add(ToUnichar("0062"), "b"); - localTable.Add(ToUnichar("0063"), "c"); - localTable.Add(ToUnichar("0064"), "d"); - localTable.Add(ToUnichar("0065"), "e"); - localTable.Add(ToUnichar("0066"), "f"); - localTable.Add(ToUnichar("0067"), "g"); - localTable.Add(ToUnichar("0068"), "h"); - localTable.Add(ToUnichar("0069"), "i"); - localTable.Add(ToUnichar("006A"), "j"); - localTable.Add(ToUnichar("006B"), "k"); - localTable.Add(ToUnichar("006C"), "l"); - localTable.Add(ToUnichar("006D"), "m"); - localTable.Add(ToUnichar("006E"), "n"); - localTable.Add(ToUnichar("006F"), "o"); - localTable.Add(ToUnichar("0070"), "p"); - localTable.Add(ToUnichar("0071"), "q"); - localTable.Add(ToUnichar("0072"), "r"); - localTable.Add(ToUnichar("0073"), "s"); - localTable.Add(ToUnichar("0074"), "t"); - localTable.Add(ToUnichar("0075"), "u"); - localTable.Add(ToUnichar("0076"), "v"); - localTable.Add(ToUnichar("0077"), "w"); - localTable.Add(ToUnichar("0078"), "x"); - localTable.Add(ToUnichar("0079"), "y"); - localTable.Add(ToUnichar("007A"), "z"); - localTable.Add(ToUnichar("00AA"), "a"); // FEMININE ORDINAL INDICATOR - localTable.Add(ToUnichar("00BA"), "o"); // MASCULINE ORDINAL INDICATOR - localTable.Add(ToUnichar("00C0"), "A"); // LATIN CAPITAL LETTER A WITH GRAVE - localTable.Add(ToUnichar("00C1"), "A"); // LATIN CAPITAL LETTER A WITH ACUTE - localTable.Add(ToUnichar("00C2"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX - localTable.Add(ToUnichar("00C3"), "A"); // LATIN CAPITAL LETTER A WITH TILDE - localTable.Add(ToUnichar("00C4"), "A"); // LATIN CAPITAL LETTER A WITH DIAERESIS - localTable.Add(ToUnichar("00C5"), "A"); // LATIN CAPITAL LETTER A WITH RING ABOVE - localTable.Add(ToUnichar("00C6"), "AE"); // LATIN CAPITAL LETTER AE -- no decomposition - localTable.Add(ToUnichar("00C7"), "C"); // LATIN CAPITAL LETTER C WITH CEDILLA - localTable.Add(ToUnichar("00C8"), "E"); // LATIN CAPITAL LETTER E WITH GRAVE - localTable.Add(ToUnichar("00C9"), "E"); // LATIN CAPITAL LETTER E WITH ACUTE - localTable.Add(ToUnichar("00CA"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX - localTable.Add(ToUnichar("00CB"), "E"); // LATIN CAPITAL LETTER E WITH DIAERESIS - localTable.Add(ToUnichar("00CC"), "I"); // LATIN CAPITAL LETTER I WITH GRAVE - localTable.Add(ToUnichar("00CD"), "I"); // LATIN CAPITAL LETTER I WITH ACUTE - localTable.Add(ToUnichar("00CE"), "I"); // LATIN CAPITAL LETTER I WITH CIRCUMFLEX - localTable.Add(ToUnichar("00CF"), "I"); // LATIN CAPITAL LETTER I WITH DIAERESIS - localTable.Add(ToUnichar("00D0"), "D"); // LATIN CAPITAL LETTER ETH -- no decomposition // Eth [D for Vietnamese] - localTable.Add(ToUnichar("00D1"), "N"); // LATIN CAPITAL LETTER N WITH TILDE - localTable.Add(ToUnichar("00D2"), "O"); // LATIN CAPITAL LETTER O WITH GRAVE - localTable.Add(ToUnichar("00D3"), "O"); // LATIN CAPITAL LETTER O WITH ACUTE - localTable.Add(ToUnichar("00D4"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX - localTable.Add(ToUnichar("00D5"), "O"); // LATIN CAPITAL LETTER O WITH TILDE - localTable.Add(ToUnichar("00D6"), "O"); // LATIN CAPITAL LETTER O WITH DIAERESIS - localTable.Add(ToUnichar("00D8"), "O"); // LATIN CAPITAL LETTER O WITH STROKE -- no decom - localTable.Add(ToUnichar("00D9"), "U"); // LATIN CAPITAL LETTER U WITH GRAVE - localTable.Add(ToUnichar("00DA"), "U"); // LATIN CAPITAL LETTER U WITH ACUTE - localTable.Add(ToUnichar("00DB"), "U"); // LATIN CAPITAL LETTER U WITH CIRCUMFLEX - localTable.Add(ToUnichar("00DC"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS - localTable.Add(ToUnichar("00DD"), "Y"); // LATIN CAPITAL LETTER Y WITH ACUTE - localTable.Add(ToUnichar("00DE"), "Th"); // LATIN CAPITAL LETTER THORN -- no decomposition; // Thorn - Could be nothing other than thorn - localTable.Add(ToUnichar("00DF"), "s"); // LATIN SMALL LETTER SHARP S -- no decomposition - localTable.Add(ToUnichar("00E0"), "a"); // LATIN SMALL LETTER A WITH GRAVE - localTable.Add(ToUnichar("00E1"), "a"); // LATIN SMALL LETTER A WITH ACUTE - localTable.Add(ToUnichar("00E2"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX - localTable.Add(ToUnichar("00E3"), "a"); // LATIN SMALL LETTER A WITH TILDE - localTable.Add(ToUnichar("00E4"), "a"); // LATIN SMALL LETTER A WITH DIAERESIS - localTable.Add(ToUnichar("00E5"), "a"); // LATIN SMALL LETTER A WITH RING ABOVE - localTable.Add(ToUnichar("00E6"), "ae"); // LATIN SMALL LETTER AE -- no decomposition - localTable.Add(ToUnichar("00E7"), "c"); // LATIN SMALL LETTER C WITH CEDILLA - localTable.Add(ToUnichar("00E8"), "e"); // LATIN SMALL LETTER E WITH GRAVE - localTable.Add(ToUnichar("00E9"), "e"); // LATIN SMALL LETTER E WITH ACUTE - localTable.Add(ToUnichar("00EA"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX - localTable.Add(ToUnichar("00EB"), "e"); // LATIN SMALL LETTER E WITH DIAERESIS - localTable.Add(ToUnichar("00EC"), "i"); // LATIN SMALL LETTER I WITH GRAVE - localTable.Add(ToUnichar("00ED"), "i"); // LATIN SMALL LETTER I WITH ACUTE - localTable.Add(ToUnichar("00EE"), "i"); // LATIN SMALL LETTER I WITH CIRCUMFLEX - localTable.Add(ToUnichar("00EF"), "i"); // LATIN SMALL LETTER I WITH DIAERESIS - localTable.Add(ToUnichar("00F0"), "d"); // LATIN SMALL LETTER ETH -- no decomposition // small eth, "d" for benefit of Vietnamese - localTable.Add(ToUnichar("00F1"), "n"); // LATIN SMALL LETTER N WITH TILDE - localTable.Add(ToUnichar("00F2"), "o"); // LATIN SMALL LETTER O WITH GRAVE - localTable.Add(ToUnichar("00F3"), "o"); // LATIN SMALL LETTER O WITH ACUTE - localTable.Add(ToUnichar("00F4"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX - localTable.Add(ToUnichar("00F5"), "o"); // LATIN SMALL LETTER O WITH TILDE - localTable.Add(ToUnichar("00F6"), "o"); // LATIN SMALL LETTER O WITH DIAERESIS - localTable.Add(ToUnichar("00F8"), "o"); // LATIN SMALL LETTER O WITH STROKE -- no decompo - localTable.Add(ToUnichar("00F9"), "u"); // LATIN SMALL LETTER U WITH GRAVE - localTable.Add(ToUnichar("00FA"), "u"); // LATIN SMALL LETTER U WITH ACUTE - localTable.Add(ToUnichar("00FB"), "u"); // LATIN SMALL LETTER U WITH CIRCUMFLEX - localTable.Add(ToUnichar("00FC"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS - localTable.Add(ToUnichar("00FD"), "y"); // LATIN SMALL LETTER Y WITH ACUTE - localTable.Add(ToUnichar("00FE"), "th"); // LATIN SMALL LETTER THORN -- no decomposition // Small thorn - localTable.Add(ToUnichar("00FF"), "y"); // LATIN SMALL LETTER Y WITH DIAERESIS - localTable.Add(ToUnichar("0100"), "A"); // LATIN CAPITAL LETTER A WITH MACRON - localTable.Add(ToUnichar("0101"), "a"); // LATIN SMALL LETTER A WITH MACRON - localTable.Add(ToUnichar("0102"), "A"); // LATIN CAPITAL LETTER A WITH BREVE - localTable.Add(ToUnichar("0103"), "a"); // LATIN SMALL LETTER A WITH BREVE - localTable.Add(ToUnichar("0104"), "A"); // LATIN CAPITAL LETTER A WITH OGONEK - localTable.Add(ToUnichar("0105"), "a"); // LATIN SMALL LETTER A WITH OGONEK - localTable.Add(ToUnichar("0106"), "C"); // LATIN CAPITAL LETTER C WITH ACUTE - localTable.Add(ToUnichar("0107"), "c"); // LATIN SMALL LETTER C WITH ACUTE - localTable.Add(ToUnichar("0108"), "C"); // LATIN CAPITAL LETTER C WITH CIRCUMFLEX - localTable.Add(ToUnichar("0109"), "c"); // LATIN SMALL LETTER C WITH CIRCUMFLEX - localTable.Add(ToUnichar("010A"), "C"); // LATIN CAPITAL LETTER C WITH DOT ABOVE - localTable.Add(ToUnichar("010B"), "c"); // LATIN SMALL LETTER C WITH DOT ABOVE - localTable.Add(ToUnichar("010C"), "C"); // LATIN CAPITAL LETTER C WITH CARON - localTable.Add(ToUnichar("010D"), "c"); // LATIN SMALL LETTER C WITH CARON - localTable.Add(ToUnichar("010E"), "D"); // LATIN CAPITAL LETTER D WITH CARON - localTable.Add(ToUnichar("010F"), "d"); // LATIN SMALL LETTER D WITH CARON - localTable.Add(ToUnichar("0110"), "D"); // LATIN CAPITAL LETTER D WITH STROKE -- no decomposition // Capital D with stroke - localTable.Add(ToUnichar("0111"), "d"); // LATIN SMALL LETTER D WITH STROKE -- no decomposition // small D with stroke - localTable.Add(ToUnichar("0112"), "E"); // LATIN CAPITAL LETTER E WITH MACRON - localTable.Add(ToUnichar("0113"), "e"); // LATIN SMALL LETTER E WITH MACRON - localTable.Add(ToUnichar("0114"), "E"); // LATIN CAPITAL LETTER E WITH BREVE - localTable.Add(ToUnichar("0115"), "e"); // LATIN SMALL LETTER E WITH BREVE - localTable.Add(ToUnichar("0116"), "E"); // LATIN CAPITAL LETTER E WITH DOT ABOVE - localTable.Add(ToUnichar("0117"), "e"); // LATIN SMALL LETTER E WITH DOT ABOVE - localTable.Add(ToUnichar("0118"), "E"); // LATIN CAPITAL LETTER E WITH OGONEK - localTable.Add(ToUnichar("0119"), "e"); // LATIN SMALL LETTER E WITH OGONEK - localTable.Add(ToUnichar("011A"), "E"); // LATIN CAPITAL LETTER E WITH CARON - localTable.Add(ToUnichar("011B"), "e"); // LATIN SMALL LETTER E WITH CARON - localTable.Add(ToUnichar("011C"), "G"); // LATIN CAPITAL LETTER G WITH CIRCUMFLEX - localTable.Add(ToUnichar("011D"), "g"); // LATIN SMALL LETTER G WITH CIRCUMFLEX - localTable.Add(ToUnichar("011E"), "G"); // LATIN CAPITAL LETTER G WITH BREVE - localTable.Add(ToUnichar("011F"), "g"); // LATIN SMALL LETTER G WITH BREVE - localTable.Add(ToUnichar("0120"), "G"); // LATIN CAPITAL LETTER G WITH DOT ABOVE - localTable.Add(ToUnichar("0121"), "g"); // LATIN SMALL LETTER G WITH DOT ABOVE - localTable.Add(ToUnichar("0122"), "G"); // LATIN CAPITAL LETTER G WITH CEDILLA - localTable.Add(ToUnichar("0123"), "g"); // LATIN SMALL LETTER G WITH CEDILLA - localTable.Add(ToUnichar("0124"), "H"); // LATIN CAPITAL LETTER H WITH CIRCUMFLEX - localTable.Add(ToUnichar("0125"), "h"); // LATIN SMALL LETTER H WITH CIRCUMFLEX - localTable.Add(ToUnichar("0126"), "H"); // LATIN CAPITAL LETTER H WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0127"), "h"); // LATIN SMALL LETTER H WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0128"), "I"); // LATIN CAPITAL LETTER I WITH TILDE - localTable.Add(ToUnichar("0129"), "i"); // LATIN SMALL LETTER I WITH TILDE - localTable.Add(ToUnichar("012A"), "I"); // LATIN CAPITAL LETTER I WITH MACRON - localTable.Add(ToUnichar("012B"), "i"); // LATIN SMALL LETTER I WITH MACRON - localTable.Add(ToUnichar("012C"), "I"); // LATIN CAPITAL LETTER I WITH BREVE - localTable.Add(ToUnichar("012D"), "i"); // LATIN SMALL LETTER I WITH BREVE - localTable.Add(ToUnichar("012E"), "I"); // LATIN CAPITAL LETTER I WITH OGONEK - localTable.Add(ToUnichar("012F"), "i"); // LATIN SMALL LETTER I WITH OGONEK - localTable.Add(ToUnichar("0130"), "I"); // LATIN CAPITAL LETTER I WITH DOT ABOVE - localTable.Add(ToUnichar("0131"), "i"); // LATIN SMALL LETTER DOTLESS I -- no decomposition - localTable.Add(ToUnichar("0132"), "I"); // LATIN CAPITAL LIGATURE IJ - localTable.Add(ToUnichar("0133"), "i"); // LATIN SMALL LIGATURE IJ - localTable.Add(ToUnichar("0134"), "J"); // LATIN CAPITAL LETTER J WITH CIRCUMFLEX - localTable.Add(ToUnichar("0135"), "j"); // LATIN SMALL LETTER J WITH CIRCUMFLEX - localTable.Add(ToUnichar("0136"), "K"); // LATIN CAPITAL LETTER K WITH CEDILLA - localTable.Add(ToUnichar("0137"), "k"); // LATIN SMALL LETTER K WITH CEDILLA - localTable.Add(ToUnichar("0138"), "k"); // LATIN SMALL LETTER KRA -- no decomposition - localTable.Add(ToUnichar("0139"), "L"); // LATIN CAPITAL LETTER L WITH ACUTE - localTable.Add(ToUnichar("013A"), "l"); // LATIN SMALL LETTER L WITH ACUTE - localTable.Add(ToUnichar("013B"), "L"); // LATIN CAPITAL LETTER L WITH CEDILLA - localTable.Add(ToUnichar("013C"), "l"); // LATIN SMALL LETTER L WITH CEDILLA - localTable.Add(ToUnichar("013D"), "L"); // LATIN CAPITAL LETTER L WITH CARON - localTable.Add(ToUnichar("013E"), "l"); // LATIN SMALL LETTER L WITH CARON - localTable.Add(ToUnichar("013F"), "L"); // LATIN CAPITAL LETTER L WITH MIDDLE DOT - localTable.Add(ToUnichar("0140"), "l"); // LATIN SMALL LETTER L WITH MIDDLE DOT - localTable.Add(ToUnichar("0141"), "L"); // LATIN CAPITAL LETTER L WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0142"), "l"); // LATIN SMALL LETTER L WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0143"), "N"); // LATIN CAPITAL LETTER N WITH ACUTE - localTable.Add(ToUnichar("0144"), "n"); // LATIN SMALL LETTER N WITH ACUTE - localTable.Add(ToUnichar("0145"), "N"); // LATIN CAPITAL LETTER N WITH CEDILLA - localTable.Add(ToUnichar("0146"), "n"); // LATIN SMALL LETTER N WITH CEDILLA - localTable.Add(ToUnichar("0147"), "N"); // LATIN CAPITAL LETTER N WITH CARON - localTable.Add(ToUnichar("0148"), "n"); // LATIN SMALL LETTER N WITH CARON - localTable.Add(ToUnichar("0149"), "'n"); // LATIN SMALL LETTER N PRECEDED BY APOSTROPHE ; - localTable.Add(ToUnichar("014A"), "NG"); // LATIN CAPITAL LETTER ENG -- no decomposition ; - localTable.Add(ToUnichar("014B"), "ng"); // LATIN SMALL LETTER ENG -- no decomposition ; - localTable.Add(ToUnichar("014C"), "O"); // LATIN CAPITAL LETTER O WITH MACRON - localTable.Add(ToUnichar("014D"), "o"); // LATIN SMALL LETTER O WITH MACRON - localTable.Add(ToUnichar("014E"), "O"); // LATIN CAPITAL LETTER O WITH BREVE - localTable.Add(ToUnichar("014F"), "o"); // LATIN SMALL LETTER O WITH BREVE - localTable.Add(ToUnichar("0150"), "O"); // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE - localTable.Add(ToUnichar("0151"), "o"); // LATIN SMALL LETTER O WITH DOUBLE ACUTE - localTable.Add(ToUnichar("0152"), "OE"); // LATIN CAPITAL LIGATURE OE -- no decomposition - localTable.Add(ToUnichar("0153"), "oe"); // LATIN SMALL LIGATURE OE -- no decomposition - localTable.Add(ToUnichar("0154"), "R"); // LATIN CAPITAL LETTER R WITH ACUTE - localTable.Add(ToUnichar("0155"), "r"); // LATIN SMALL LETTER R WITH ACUTE - localTable.Add(ToUnichar("0156"), "R"); // LATIN CAPITAL LETTER R WITH CEDILLA - localTable.Add(ToUnichar("0157"), "r"); // LATIN SMALL LETTER R WITH CEDILLA - localTable.Add(ToUnichar("0158"), "R"); // LATIN CAPITAL LETTER R WITH CARON - localTable.Add(ToUnichar("0159"), "r"); // LATIN SMALL LETTER R WITH CARON - localTable.Add(ToUnichar("015A"), "S"); // LATIN CAPITAL LETTER S WITH ACUTE - localTable.Add(ToUnichar("015B"), "s"); // LATIN SMALL LETTER S WITH ACUTE - localTable.Add(ToUnichar("015C"), "S"); // LATIN CAPITAL LETTER S WITH CIRCUMFLEX - localTable.Add(ToUnichar("015D"), "s"); // LATIN SMALL LETTER S WITH CIRCUMFLEX - localTable.Add(ToUnichar("015E"), "S"); // LATIN CAPITAL LETTER S WITH CEDILLA - localTable.Add(ToUnichar("015F"), "s"); // LATIN SMALL LETTER S WITH CEDILLA - localTable.Add(ToUnichar("0160"), "S"); // LATIN CAPITAL LETTER S WITH CARON - localTable.Add(ToUnichar("0161"), "s"); // LATIN SMALL LETTER S WITH CARON - localTable.Add(ToUnichar("0162"), "T"); // LATIN CAPITAL LETTER T WITH CEDILLA - localTable.Add(ToUnichar("0163"), "t"); // LATIN SMALL LETTER T WITH CEDILLA - localTable.Add(ToUnichar("0164"), "T"); // LATIN CAPITAL LETTER T WITH CARON - localTable.Add(ToUnichar("0165"), "t"); // LATIN SMALL LETTER T WITH CARON - localTable.Add(ToUnichar("0166"), "T"); // LATIN CAPITAL LETTER T WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0167"), "t"); // LATIN SMALL LETTER T WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0168"), "U"); // LATIN CAPITAL LETTER U WITH TILDE - localTable.Add(ToUnichar("0169"), "u"); // LATIN SMALL LETTER U WITH TILDE - localTable.Add(ToUnichar("016A"), "U"); // LATIN CAPITAL LETTER U WITH MACRON - localTable.Add(ToUnichar("016B"), "u"); // LATIN SMALL LETTER U WITH MACRON - localTable.Add(ToUnichar("016C"), "U"); // LATIN CAPITAL LETTER U WITH BREVE - localTable.Add(ToUnichar("016D"), "u"); // LATIN SMALL LETTER U WITH BREVE - localTable.Add(ToUnichar("016E"), "U"); // LATIN CAPITAL LETTER U WITH RING ABOVE - localTable.Add(ToUnichar("016F"), "u"); // LATIN SMALL LETTER U WITH RING ABOVE - localTable.Add(ToUnichar("0170"), "U"); // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE - localTable.Add(ToUnichar("0171"), "u"); // LATIN SMALL LETTER U WITH DOUBLE ACUTE - localTable.Add(ToUnichar("0172"), "U"); // LATIN CAPITAL LETTER U WITH OGONEK - localTable.Add(ToUnichar("0173"), "u"); // LATIN SMALL LETTER U WITH OGONEK - localTable.Add(ToUnichar("0174"), "W"); // LATIN CAPITAL LETTER W WITH CIRCUMFLEX - localTable.Add(ToUnichar("0175"), "w"); // LATIN SMALL LETTER W WITH CIRCUMFLEX - localTable.Add(ToUnichar("0176"), "Y"); // LATIN CAPITAL LETTER Y WITH CIRCUMFLEX - localTable.Add(ToUnichar("0177"), "y"); // LATIN SMALL LETTER Y WITH CIRCUMFLEX - localTable.Add(ToUnichar("0178"), "Y"); // LATIN CAPITAL LETTER Y WITH DIAERESIS - localTable.Add(ToUnichar("0179"), "Z"); // LATIN CAPITAL LETTER Z WITH ACUTE - localTable.Add(ToUnichar("017A"), "z"); // LATIN SMALL LETTER Z WITH ACUTE - localTable.Add(ToUnichar("017B"), "Z"); // LATIN CAPITAL LETTER Z WITH DOT ABOVE - localTable.Add(ToUnichar("017C"), "z"); // LATIN SMALL LETTER Z WITH DOT ABOVE - localTable.Add(ToUnichar("017D"), "Z"); // LATIN CAPITAL LETTER Z WITH CARON - localTable.Add(ToUnichar("017E"), "z"); // LATIN SMALL LETTER Z WITH CARON - localTable.Add(ToUnichar("017F"), "s"); // LATIN SMALL LETTER LONG S - localTable.Add(ToUnichar("0180"), "b"); // LATIN SMALL LETTER B WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0181"), "B"); // LATIN CAPITAL LETTER B WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0182"), "B"); // LATIN CAPITAL LETTER B WITH TOPBAR -- no decomposition - localTable.Add(ToUnichar("0183"), "b"); // LATIN SMALL LETTER B WITH TOPBAR -- no decomposition - localTable.Add(ToUnichar("0184"), "6"); // LATIN CAPITAL LETTER TONE SIX -- no decomposition - localTable.Add(ToUnichar("0185"), "6"); // LATIN SMALL LETTER TONE SIX -- no decomposition - localTable.Add(ToUnichar("0186"), "O"); // LATIN CAPITAL LETTER OPEN O -- no decomposition - localTable.Add(ToUnichar("0187"), "C"); // LATIN CAPITAL LETTER C WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0188"), "c"); // LATIN SMALL LETTER C WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0189"), "D"); // LATIN CAPITAL LETTER AFRICAN D -- no decomposition - localTable.Add(ToUnichar("018A"), "D"); // LATIN CAPITAL LETTER D WITH HOOK -- no decomposition - localTable.Add(ToUnichar("018B"), "D"); // LATIN CAPITAL LETTER D WITH TOPBAR -- no decomposition - localTable.Add(ToUnichar("018C"), "d"); // LATIN SMALL LETTER D WITH TOPBAR -- no decomposition - localTable.Add(ToUnichar("018D"), "d"); // LATIN SMALL LETTER TURNED DELTA -- no decomposition - localTable.Add(ToUnichar("018E"), "E"); // LATIN CAPITAL LETTER REVERSED E -- no decomposition - localTable.Add(ToUnichar("018F"), "E"); // LATIN CAPITAL LETTER SCHWA -- no decomposition - localTable.Add(ToUnichar("0190"), "E"); // LATIN CAPITAL LETTER OPEN E -- no decomposition - localTable.Add(ToUnichar("0191"), "F"); // LATIN CAPITAL LETTER F WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0192"), "f"); // LATIN SMALL LETTER F WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0193"), "G"); // LATIN CAPITAL LETTER G WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0194"), "G"); // LATIN CAPITAL LETTER GAMMA -- no decomposition - localTable.Add(ToUnichar("0195"), "hv"); // LATIN SMALL LETTER HV -- no decomposition - localTable.Add(ToUnichar("0196"), "I"); // LATIN CAPITAL LETTER IOTA -- no decomposition - localTable.Add(ToUnichar("0197"), "I"); // LATIN CAPITAL LETTER I WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0198"), "K"); // LATIN CAPITAL LETTER K WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0199"), "k"); // LATIN SMALL LETTER K WITH HOOK -- no decomposition - localTable.Add(ToUnichar("019A"), "l"); // LATIN SMALL LETTER L WITH BAR -- no decomposition - localTable.Add(ToUnichar("019B"), "l"); // LATIN SMALL LETTER LAMBDA WITH STROKE -- no decomposition - localTable.Add(ToUnichar("019C"), "M"); // LATIN CAPITAL LETTER TURNED M -- no decomposition - localTable.Add(ToUnichar("019D"), "N"); // LATIN CAPITAL LETTER N WITH LEFT HOOK -- no decomposition - localTable.Add(ToUnichar("019E"), "n"); // LATIN SMALL LETTER N WITH LONG RIGHT LEG -- no decomposition - localTable.Add(ToUnichar("019F"), "O"); // LATIN CAPITAL LETTER O WITH MIDDLE TILDE -- no decomposition - localTable.Add(ToUnichar("01A0"), "O"); // LATIN CAPITAL LETTER O WITH HORN - localTable.Add(ToUnichar("01A1"), "o"); // LATIN SMALL LETTER O WITH HORN - localTable.Add(ToUnichar("01A2"), "OI"); // LATIN CAPITAL LETTER OI -- no decomposition - localTable.Add(ToUnichar("01A3"), "oi"); // LATIN SMALL LETTER OI -- no decomposition - localTable.Add(ToUnichar("01A4"), "P"); // LATIN CAPITAL LETTER P WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01A5"), "p"); // LATIN SMALL LETTER P WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01A6"), "YR"); // LATIN LETTER YR -- no decomposition - localTable.Add(ToUnichar("01A7"), "2"); // LATIN CAPITAL LETTER TONE TWO -- no decomposition - localTable.Add(ToUnichar("01A8"), "2"); // LATIN SMALL LETTER TONE TWO -- no decomposition - localTable.Add(ToUnichar("01A9"), "S"); // LATIN CAPITAL LETTER ESH -- no decomposition - localTable.Add(ToUnichar("01AA"), "s"); // LATIN LETTER REVERSED ESH LOOP -- no decomposition - localTable.Add(ToUnichar("01AB"), "t"); // LATIN SMALL LETTER T WITH PALATAL HOOK -- no decomposition - localTable.Add(ToUnichar("01AC"), "T"); // LATIN CAPITAL LETTER T WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01AD"), "t"); // LATIN SMALL LETTER T WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01AE"), "T"); // LATIN CAPITAL LETTER T WITH RETROFLEX HOOK -- no decomposition - localTable.Add(ToUnichar("01AF"), "U"); // LATIN CAPITAL LETTER U WITH HORN - localTable.Add(ToUnichar("01B0"), "u"); // LATIN SMALL LETTER U WITH HORN - localTable.Add(ToUnichar("01B1"), "u"); // LATIN CAPITAL LETTER UPSILON -- no decomposition - localTable.Add(ToUnichar("01B2"), "V"); // LATIN CAPITAL LETTER V WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01B3"), "Y"); // LATIN CAPITAL LETTER Y WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01B4"), "y"); // LATIN SMALL LETTER Y WITH HOOK -- no decomposition - localTable.Add(ToUnichar("01B5"), "Z"); // LATIN CAPITAL LETTER Z WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01B6"), "z"); // LATIN SMALL LETTER Z WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01B7"), "Z"); // LATIN CAPITAL LETTER EZH -- no decomposition - localTable.Add(ToUnichar("01B8"), "Z"); // LATIN CAPITAL LETTER EZH REVERSED -- no decomposition - localTable.Add(ToUnichar("01B9"), "Z"); // LATIN SMALL LETTER EZH REVERSED -- no decomposition - localTable.Add(ToUnichar("01BA"), "z"); // LATIN SMALL LETTER EZH WITH TAIL -- no decomposition - localTable.Add(ToUnichar("01BB"), "2"); // LATIN LETTER TWO WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01BC"), "5"); // LATIN CAPITAL LETTER TONE FIVE -- no decomposition - localTable.Add(ToUnichar("01BD"), "5"); // LATIN SMALL LETTER TONE FIVE -- no decomposition - //localTable.Add(ToUnichar("01BE"), "´"); // LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01BF"), "w"); // LATIN LETTER WYNN -- no decomposition - localTable.Add(ToUnichar("01C0"), "!"); // LATIN LETTER DENTAL CLICK -- no decomposition - localTable.Add(ToUnichar("01C1"), "!"); // LATIN LETTER LATERAL CLICK -- no decomposition - localTable.Add(ToUnichar("01C2"), "!"); // LATIN LETTER ALVEOLAR CLICK -- no decomposition - localTable.Add(ToUnichar("01C3"), "!"); // LATIN LETTER RETROFLEX CLICK -- no decomposition - localTable.Add(ToUnichar("01C4"), "DZ"); // LATIN CAPITAL LETTER DZ WITH CARON - localTable.Add(ToUnichar("01C5"), "DZ"); // LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON - localTable.Add(ToUnichar("01C6"), "d"); // LATIN SMALL LETTER DZ WITH CARON - localTable.Add(ToUnichar("01C7"), "Lj"); // LATIN CAPITAL LETTER LJ - localTable.Add(ToUnichar("01C8"), "Lj"); // LATIN CAPITAL LETTER L WITH SMALL LETTER J - localTable.Add(ToUnichar("01C9"), "lj"); // LATIN SMALL LETTER LJ - localTable.Add(ToUnichar("01CA"), "NJ"); // LATIN CAPITAL LETTER NJ - localTable.Add(ToUnichar("01CB"), "NJ"); // LATIN CAPITAL LETTER N WITH SMALL LETTER J - localTable.Add(ToUnichar("01CC"), "nj"); // LATIN SMALL LETTER NJ - localTable.Add(ToUnichar("01CD"), "A"); // LATIN CAPITAL LETTER A WITH CARON - localTable.Add(ToUnichar("01CE"), "a"); // LATIN SMALL LETTER A WITH CARON - localTable.Add(ToUnichar("01CF"), "I"); // LATIN CAPITAL LETTER I WITH CARON - localTable.Add(ToUnichar("01D0"), "i"); // LATIN SMALL LETTER I WITH CARON - localTable.Add(ToUnichar("01D1"), "O"); // LATIN CAPITAL LETTER O WITH CARON - localTable.Add(ToUnichar("01D2"), "o"); // LATIN SMALL LETTER O WITH CARON - localTable.Add(ToUnichar("01D3"), "U"); // LATIN CAPITAL LETTER U WITH CARON - localTable.Add(ToUnichar("01D4"), "u"); // LATIN SMALL LETTER U WITH CARON - localTable.Add(ToUnichar("01D5"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("01D6"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("01D7"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE - localTable.Add(ToUnichar("01D8"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE - localTable.Add(ToUnichar("01D9"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON - localTable.Add(ToUnichar("01DA"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS AND CARON - localTable.Add(ToUnichar("01DB"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE - localTable.Add(ToUnichar("01DC"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE - localTable.Add(ToUnichar("01DD"), "e"); // LATIN SMALL LETTER TURNED E -- no decomposition - localTable.Add(ToUnichar("01DE"), "A"); // LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("01DF"), "a"); // LATIN SMALL LETTER A WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("01E0"), "A"); // LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON - localTable.Add(ToUnichar("01E1"), "a"); // LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON - localTable.Add(ToUnichar("01E2"), "AE"); // LATIN CAPITAL LETTER AE WITH MACRON - localTable.Add(ToUnichar("01E3"), "ae"); // LATIN SMALL LETTER AE WITH MACRON - localTable.Add(ToUnichar("01E4"), "G"); // LATIN CAPITAL LETTER G WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01E5"), "g"); // LATIN SMALL LETTER G WITH STROKE -- no decomposition - localTable.Add(ToUnichar("01E6"), "G"); // LATIN CAPITAL LETTER G WITH CARON - localTable.Add(ToUnichar("01E7"), "g"); // LATIN SMALL LETTER G WITH CARON - localTable.Add(ToUnichar("01E8"), "K"); // LATIN CAPITAL LETTER K WITH CARON - localTable.Add(ToUnichar("01E9"), "k"); // LATIN SMALL LETTER K WITH CARON - localTable.Add(ToUnichar("01EA"), "O"); // LATIN CAPITAL LETTER O WITH OGONEK - localTable.Add(ToUnichar("01EB"), "o"); // LATIN SMALL LETTER O WITH OGONEK - localTable.Add(ToUnichar("01EC"), "O"); // LATIN CAPITAL LETTER O WITH OGONEK AND MACRON - localTable.Add(ToUnichar("01ED"), "o"); // LATIN SMALL LETTER O WITH OGONEK AND MACRON - localTable.Add(ToUnichar("01EE"), "Z"); // LATIN CAPITAL LETTER EZH WITH CARON - localTable.Add(ToUnichar("01EF"), "Z"); // LATIN SMALL LETTER EZH WITH CARON - localTable.Add(ToUnichar("01F0"), "j"); // LATIN SMALL LETTER J WITH CARON - localTable.Add(ToUnichar("01F1"), "DZ"); // LATIN CAPITAL LETTER DZ - localTable.Add(ToUnichar("01F2"), "DZ"); // LATIN CAPITAL LETTER D WITH SMALL LETTER Z - localTable.Add(ToUnichar("01F3"), "dz"); // LATIN SMALL LETTER DZ - localTable.Add(ToUnichar("01F4"), "G"); // LATIN CAPITAL LETTER G WITH ACUTE - localTable.Add(ToUnichar("01F5"), "g"); // LATIN SMALL LETTER G WITH ACUTE - localTable.Add(ToUnichar("01F6"), "hv"); // LATIN CAPITAL LETTER HWAIR -- no decomposition - localTable.Add(ToUnichar("01F7"), "w"); // LATIN CAPITAL LETTER WYNN -- no decomposition - localTable.Add(ToUnichar("01F8"), "N"); // LATIN CAPITAL LETTER N WITH GRAVE - localTable.Add(ToUnichar("01F9"), "n"); // LATIN SMALL LETTER N WITH GRAVE - localTable.Add(ToUnichar("01FA"), "A"); // LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE - localTable.Add(ToUnichar("01FB"), "a"); // LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE - localTable.Add(ToUnichar("01FC"), "AE"); // LATIN CAPITAL LETTER AE WITH ACUTE - localTable.Add(ToUnichar("01FD"), "ae"); // LATIN SMALL LETTER AE WITH ACUTE - localTable.Add(ToUnichar("01FE"), "O"); // LATIN CAPITAL LETTER O WITH STROKE AND ACUTE - localTable.Add(ToUnichar("01FF"), "o"); // LATIN SMALL LETTER O WITH STROKE AND ACUTE - localTable.Add(ToUnichar("0200"), "A"); // LATIN CAPITAL LETTER A WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0201"), "a"); // LATIN SMALL LETTER A WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0202"), "A"); // LATIN CAPITAL LETTER A WITH INVERTED BREVE - localTable.Add(ToUnichar("0203"), "a"); // LATIN SMALL LETTER A WITH INVERTED BREVE - localTable.Add(ToUnichar("0204"), "E"); // LATIN CAPITAL LETTER E WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0205"), "e"); // LATIN SMALL LETTER E WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0206"), "E"); // LATIN CAPITAL LETTER E WITH INVERTED BREVE - localTable.Add(ToUnichar("0207"), "e"); // LATIN SMALL LETTER E WITH INVERTED BREVE - localTable.Add(ToUnichar("0208"), "I"); // LATIN CAPITAL LETTER I WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0209"), "i"); // LATIN SMALL LETTER I WITH DOUBLE GRAVE - localTable.Add(ToUnichar("020A"), "I"); // LATIN CAPITAL LETTER I WITH INVERTED BREVE - localTable.Add(ToUnichar("020B"), "i"); // LATIN SMALL LETTER I WITH INVERTED BREVE - localTable.Add(ToUnichar("020C"), "O"); // LATIN CAPITAL LETTER O WITH DOUBLE GRAVE - localTable.Add(ToUnichar("020D"), "o"); // LATIN SMALL LETTER O WITH DOUBLE GRAVE - localTable.Add(ToUnichar("020E"), "O"); // LATIN CAPITAL LETTER O WITH INVERTED BREVE - localTable.Add(ToUnichar("020F"), "o"); // LATIN SMALL LETTER O WITH INVERTED BREVE - localTable.Add(ToUnichar("0210"), "R"); // LATIN CAPITAL LETTER R WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0211"), "r"); // LATIN SMALL LETTER R WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0212"), "R"); // LATIN CAPITAL LETTER R WITH INVERTED BREVE - localTable.Add(ToUnichar("0213"), "r"); // LATIN SMALL LETTER R WITH INVERTED BREVE - localTable.Add(ToUnichar("0214"), "U"); // LATIN CAPITAL LETTER U WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0215"), "u"); // LATIN SMALL LETTER U WITH DOUBLE GRAVE - localTable.Add(ToUnichar("0216"), "U"); // LATIN CAPITAL LETTER U WITH INVERTED BREVE - localTable.Add(ToUnichar("0217"), "u"); // LATIN SMALL LETTER U WITH INVERTED BREVE - localTable.Add(ToUnichar("0218"), "S"); // LATIN CAPITAL LETTER S WITH COMMA BELOW - localTable.Add(ToUnichar("0219"), "s"); // LATIN SMALL LETTER S WITH COMMA BELOW - localTable.Add(ToUnichar("021A"), "T"); // LATIN CAPITAL LETTER T WITH COMMA BELOW - localTable.Add(ToUnichar("021B"), "t"); // LATIN SMALL LETTER T WITH COMMA BELOW - localTable.Add(ToUnichar("021C"), "Z"); // LATIN CAPITAL LETTER YOGH -- no decomposition - localTable.Add(ToUnichar("021D"), "z"); // LATIN SMALL LETTER YOGH -- no decomposition - localTable.Add(ToUnichar("021E"), "H"); // LATIN CAPITAL LETTER H WITH CARON - localTable.Add(ToUnichar("021F"), "h"); // LATIN SMALL LETTER H WITH CARON - localTable.Add(ToUnichar("0220"), "N"); // LATIN CAPITAL LETTER N WITH LONG RIGHT LEG -- no decomposition - localTable.Add(ToUnichar("0221"), "d"); // LATIN SMALL LETTER D WITH CURL -- no decomposition - localTable.Add(ToUnichar("0222"), "OU"); // LATIN CAPITAL LETTER OU -- no decomposition - localTable.Add(ToUnichar("0223"), "ou"); // LATIN SMALL LETTER OU -- no decomposition - localTable.Add(ToUnichar("0224"), "Z"); // LATIN CAPITAL LETTER Z WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0225"), "z"); // LATIN SMALL LETTER Z WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0226"), "A"); // LATIN CAPITAL LETTER A WITH DOT ABOVE - localTable.Add(ToUnichar("0227"), "a"); // LATIN SMALL LETTER A WITH DOT ABOVE - localTable.Add(ToUnichar("0228"), "E"); // LATIN CAPITAL LETTER E WITH CEDILLA - localTable.Add(ToUnichar("0229"), "e"); // LATIN SMALL LETTER E WITH CEDILLA - localTable.Add(ToUnichar("022A"), "O"); // LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("022B"), "o"); // LATIN SMALL LETTER O WITH DIAERESIS AND MACRON - localTable.Add(ToUnichar("022C"), "O"); // LATIN CAPITAL LETTER O WITH TILDE AND MACRON - localTable.Add(ToUnichar("022D"), "o"); // LATIN SMALL LETTER O WITH TILDE AND MACRON - localTable.Add(ToUnichar("022E"), "O"); // LATIN CAPITAL LETTER O WITH DOT ABOVE - localTable.Add(ToUnichar("022F"), "o"); // LATIN SMALL LETTER O WITH DOT ABOVE - localTable.Add(ToUnichar("0230"), "O"); // LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON - localTable.Add(ToUnichar("0231"), "o"); // LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON - localTable.Add(ToUnichar("0232"), "Y"); // LATIN CAPITAL LETTER Y WITH MACRON - localTable.Add(ToUnichar("0233"), "y"); // LATIN SMALL LETTER Y WITH MACRON - localTable.Add(ToUnichar("0234"), "l"); // LATIN SMALL LETTER L WITH CURL -- no decomposition - localTable.Add(ToUnichar("0235"), "n"); // LATIN SMALL LETTER N WITH CURL -- no decomposition - localTable.Add(ToUnichar("0236"), "t"); // LATIN SMALL LETTER T WITH CURL -- no decomposition - localTable.Add(ToUnichar("0250"), "a"); // LATIN SMALL LETTER TURNED A -- no decomposition - localTable.Add(ToUnichar("0251"), "a"); // LATIN SMALL LETTER ALPHA -- no decomposition - localTable.Add(ToUnichar("0252"), "a"); // LATIN SMALL LETTER TURNED ALPHA -- no decomposition - localTable.Add(ToUnichar("0253"), "b"); // LATIN SMALL LETTER B WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0254"), "o"); // LATIN SMALL LETTER OPEN O -- no decomposition - localTable.Add(ToUnichar("0255"), "c"); // LATIN SMALL LETTER C WITH CURL -- no decomposition - localTable.Add(ToUnichar("0256"), "d"); // LATIN SMALL LETTER D WITH TAIL -- no decomposition - localTable.Add(ToUnichar("0257"), "d"); // LATIN SMALL LETTER D WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0258"), "e"); // LATIN SMALL LETTER REVERSED E -- no decomposition - localTable.Add(ToUnichar("0259"), "e"); // LATIN SMALL LETTER SCHWA -- no decomposition - localTable.Add(ToUnichar("025A"), "e"); // LATIN SMALL LETTER SCHWA WITH HOOK -- no decomposition - localTable.Add(ToUnichar("025B"), "e"); // LATIN SMALL LETTER OPEN E -- no decomposition - localTable.Add(ToUnichar("025C"), "e"); // LATIN SMALL LETTER REVERSED OPEN E -- no decomposition - localTable.Add(ToUnichar("025D"), "e"); // LATIN SMALL LETTER REVERSED OPEN E WITH HOOK -- no decomposition - localTable.Add(ToUnichar("025E"), "e"); // LATIN SMALL LETTER CLOSED REVERSED OPEN E -- no decomposition - localTable.Add(ToUnichar("025F"), "j"); // LATIN SMALL LETTER DOTLESS J WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0260"), "g"); // LATIN SMALL LETTER G WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0261"), "g"); // LATIN SMALL LETTER SCRIPT G -- no decomposition - localTable.Add(ToUnichar("0262"), "G"); // LATIN LETTER SMALL CAPITAL G -- no decomposition - localTable.Add(ToUnichar("0263"), "g"); // LATIN SMALL LETTER GAMMA -- no decomposition - localTable.Add(ToUnichar("0264"), "y"); // LATIN SMALL LETTER RAMS HORN -- no decomposition - localTable.Add(ToUnichar("0265"), "h"); // LATIN SMALL LETTER TURNED H -- no decomposition - localTable.Add(ToUnichar("0266"), "h"); // LATIN SMALL LETTER H WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0267"), "h"); // LATIN SMALL LETTER HENG WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0268"), "i"); // LATIN SMALL LETTER I WITH STROKE -- no decomposition - localTable.Add(ToUnichar("0269"), "i"); // LATIN SMALL LETTER IOTA -- no decomposition - localTable.Add(ToUnichar("026A"), "I"); // LATIN LETTER SMALL CAPITAL I -- no decomposition - localTable.Add(ToUnichar("026B"), "l"); // LATIN SMALL LETTER L WITH MIDDLE TILDE -- no decomposition - localTable.Add(ToUnichar("026C"), "l"); // LATIN SMALL LETTER L WITH BELT -- no decomposition - localTable.Add(ToUnichar("026D"), "l"); // LATIN SMALL LETTER L WITH RETROFLEX HOOK -- no decomposition - localTable.Add(ToUnichar("026E"), "lz"); // LATIN SMALL LETTER LEZH -- no decomposition - localTable.Add(ToUnichar("026F"), "m"); // LATIN SMALL LETTER TURNED M -- no decomposition - localTable.Add(ToUnichar("0270"), "m"); // LATIN SMALL LETTER TURNED M WITH LONG LEG -- no decomposition - localTable.Add(ToUnichar("0271"), "m"); // LATIN SMALL LETTER M WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0272"), "n"); // LATIN SMALL LETTER N WITH LEFT HOOK -- no decomposition - localTable.Add(ToUnichar("0273"), "n"); // LATIN SMALL LETTER N WITH RETROFLEX HOOK -- no decomposition - localTable.Add(ToUnichar("0274"), "N"); // LATIN LETTER SMALL CAPITAL N -- no decomposition - localTable.Add(ToUnichar("0275"), "o"); // LATIN SMALL LETTER BARRED O -- no decomposition - localTable.Add(ToUnichar("0276"), "OE"); // LATIN LETTER SMALL CAPITAL OE -- no decomposition - localTable.Add(ToUnichar("0277"), "o"); // LATIN SMALL LETTER CLOSED OMEGA -- no decomposition - localTable.Add(ToUnichar("0278"), "ph"); // LATIN SMALL LETTER PHI -- no decomposition - localTable.Add(ToUnichar("0279"), "r"); // LATIN SMALL LETTER TURNED R -- no decomposition - localTable.Add(ToUnichar("027A"), "r"); // LATIN SMALL LETTER TURNED R WITH LONG LEG -- no decomposition - localTable.Add(ToUnichar("027B"), "r"); // LATIN SMALL LETTER TURNED R WITH HOOK -- no decomposition - localTable.Add(ToUnichar("027C"), "r"); // LATIN SMALL LETTER R WITH LONG LEG -- no decomposition - localTable.Add(ToUnichar("027D"), "r"); // LATIN SMALL LETTER R WITH TAIL -- no decomposition - localTable.Add(ToUnichar("027E"), "r"); // LATIN SMALL LETTER R WITH FISHHOOK -- no decomposition - localTable.Add(ToUnichar("027F"), "r"); // LATIN SMALL LETTER REVERSED R WITH FISHHOOK -- no decomposition - localTable.Add(ToUnichar("0280"), "R"); // LATIN LETTER SMALL CAPITAL R -- no decomposition - localTable.Add(ToUnichar("0281"), "r"); // LATIN LETTER SMALL CAPITAL INVERTED R -- no decomposition - localTable.Add(ToUnichar("0282"), "s"); // LATIN SMALL LETTER S WITH HOOK -- no decomposition - localTable.Add(ToUnichar("0283"), "s"); // LATIN SMALL LETTER ESH -- no decomposition - localTable.Add(ToUnichar("0284"), "j"); // LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK -- no decomposition - localTable.Add(ToUnichar("0285"), "s"); // LATIN SMALL LETTER SQUAT REVERSED ESH -- no decomposition - localTable.Add(ToUnichar("0286"), "s"); // LATIN SMALL LETTER ESH WITH CURL -- no decomposition - localTable.Add(ToUnichar("0287"), "y"); // LATIN SMALL LETTER TURNED T -- no decomposition - localTable.Add(ToUnichar("0288"), "t"); // LATIN SMALL LETTER T WITH RETROFLEX HOOK -- no decomposition - localTable.Add(ToUnichar("0289"), "u"); // LATIN SMALL LETTER U BAR -- no decomposition - localTable.Add(ToUnichar("028A"), "u"); // LATIN SMALL LETTER UPSILON -- no decomposition - localTable.Add(ToUnichar("028B"), "u"); // LATIN SMALL LETTER V WITH HOOK -- no decomposition - localTable.Add(ToUnichar("028C"), "v"); // LATIN SMALL LETTER TURNED V -- no decomposition - localTable.Add(ToUnichar("028D"), "w"); // LATIN SMALL LETTER TURNED W -- no decomposition - localTable.Add(ToUnichar("028E"), "y"); // LATIN SMALL LETTER TURNED Y -- no decomposition - localTable.Add(ToUnichar("028F"), "Y"); // LATIN LETTER SMALL CAPITAL Y -- no decomposition - localTable.Add(ToUnichar("0290"), "z"); // LATIN SMALL LETTER Z WITH RETROFLEX HOOK -- no decomposition - localTable.Add(ToUnichar("0291"), "z"); // LATIN SMALL LETTER Z WITH CURL -- no decomposition - localTable.Add(ToUnichar("0292"), "z"); // LATIN SMALL LETTER EZH -- no decomposition - localTable.Add(ToUnichar("0293"), "z"); // LATIN SMALL LETTER EZH WITH CURL -- no decomposition - localTable.Add(ToUnichar("0294"), "'"); // LATIN LETTER GLOTTAL STOP -- no decomposition - localTable.Add(ToUnichar("0295"), "'"); // LATIN LETTER PHARYNGEAL VOICED FRICATIVE -- no decomposition - localTable.Add(ToUnichar("0296"), "'"); // LATIN LETTER INVERTED GLOTTAL STOP -- no decomposition - localTable.Add(ToUnichar("0297"), "C"); // LATIN LETTER STRETCHED C -- no decomposition - //localTable.Add(ToUnichar("0298"), "O˜"); // LATIN LETTER BILABIAL CLICK -- no decomposition - localTable.Add(ToUnichar("0299"), "B"); // LATIN LETTER SMALL CAPITAL B -- no decomposition - localTable.Add(ToUnichar("029A"), "e"); // LATIN SMALL LETTER CLOSED OPEN E -- no decomposition - localTable.Add(ToUnichar("029B"), "G"); // LATIN LETTER SMALL CAPITAL G WITH HOOK -- no decomposition - localTable.Add(ToUnichar("029C"), "H"); // LATIN LETTER SMALL CAPITAL H -- no decomposition - localTable.Add(ToUnichar("029D"), "j"); // LATIN SMALL LETTER J WITH CROSSED-TAIL -- no decomposition - localTable.Add(ToUnichar("029E"), "k"); // LATIN SMALL LETTER TURNED K -- no decomposition - localTable.Add(ToUnichar("029F"), "L"); // LATIN LETTER SMALL CAPITAL L -- no decomposition - localTable.Add(ToUnichar("02A0"), "q"); // LATIN SMALL LETTER Q WITH HOOK -- no decomposition - localTable.Add(ToUnichar("02A1"), "'"); // LATIN LETTER GLOTTAL STOP WITH STROKE -- no decomposition - localTable.Add(ToUnichar("02A2"), "'"); // LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE -- no decomposition - localTable.Add(ToUnichar("02A3"), "dz"); // LATIN SMALL LETTER DZ DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02A4"), "dz"); // LATIN SMALL LETTER DEZH DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02A5"), "dz"); // LATIN SMALL LETTER DZ DIGRAPH WITH CURL -- no decomposition - localTable.Add(ToUnichar("02A6"), "ts"); // LATIN SMALL LETTER TS DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02A7"), "ts"); // LATIN SMALL LETTER TESH DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02A8"), ""); // LATIN SMALL LETTER TC DIGRAPH WITH CURL -- no decomposition - localTable.Add(ToUnichar("02A9"), "fn"); // LATIN SMALL LETTER FENG DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02AA"), "ls"); // LATIN SMALL LETTER LS DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02AB"), "lz"); // LATIN SMALL LETTER LZ DIGRAPH -- no decomposition - localTable.Add(ToUnichar("02AC"), "w"); // LATIN LETTER BILABIAL PERCUSSIVE -- no decomposition - localTable.Add(ToUnichar("02AD"), "t"); // LATIN LETTER BIDENTAL PERCUSSIVE -- no decomposition - localTable.Add(ToUnichar("02AE"), "h"); // LATIN SMALL LETTER TURNED H WITH FISHHOOK -- no decomposition - localTable.Add(ToUnichar("02AF"), "h"); // LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL -- no decomposition - localTable.Add(ToUnichar("02B0"), "h"); // MODIFIER LETTER SMALL H - localTable.Add(ToUnichar("02B1"), "h"); // MODIFIER LETTER SMALL H WITH HOOK - localTable.Add(ToUnichar("02B2"), "j"); // MODIFIER LETTER SMALL J - localTable.Add(ToUnichar("02B3"), "r"); // MODIFIER LETTER SMALL R - localTable.Add(ToUnichar("02B4"), "r"); // MODIFIER LETTER SMALL TURNED R - localTable.Add(ToUnichar("02B5"), "r"); // MODIFIER LETTER SMALL TURNED R WITH HOOK - localTable.Add(ToUnichar("02B6"), "R"); // MODIFIER LETTER SMALL CAPITAL INVERTED R - localTable.Add(ToUnichar("02B7"), "w"); // MODIFIER LETTER SMALL W - localTable.Add(ToUnichar("02B8"), "y"); // MODIFIER LETTER SMALL Y - localTable.Add(ToUnichar("02E1"), "l"); // MODIFIER LETTER SMALL L - localTable.Add(ToUnichar("02E2"), "s"); // MODIFIER LETTER SMALL S - localTable.Add(ToUnichar("02E3"), "x"); // MODIFIER LETTER SMALL X - localTable.Add(ToUnichar("02E4"), "'"); // MODIFIER LETTER SMALL REVERSED GLOTTAL STOP - localTable.Add(ToUnichar("1D00"), "A"); // LATIN LETTER SMALL CAPITAL A -- no decomposition - localTable.Add(ToUnichar("1D01"), "AE"); // LATIN LETTER SMALL CAPITAL AE -- no decomposition - localTable.Add(ToUnichar("1D02"), "ae"); // LATIN SMALL LETTER TURNED AE -- no decomposition - localTable.Add(ToUnichar("1D03"), "B"); // LATIN LETTER SMALL CAPITAL BARRED B -- no decomposition - localTable.Add(ToUnichar("1D04"), "C"); // LATIN LETTER SMALL CAPITAL C -- no decomposition - localTable.Add(ToUnichar("1D05"), "D"); // LATIN LETTER SMALL CAPITAL D -- no decomposition - localTable.Add(ToUnichar("1D06"), "TH"); // LATIN LETTER SMALL CAPITAL ETH -- no decomposition - localTable.Add(ToUnichar("1D07"), "E"); // LATIN LETTER SMALL CAPITAL E -- no decomposition - localTable.Add(ToUnichar("1D08"), "e"); // LATIN SMALL LETTER TURNED OPEN E -- no decomposition - localTable.Add(ToUnichar("1D09"), "i"); // LATIN SMALL LETTER TURNED I -- no decomposition - localTable.Add(ToUnichar("1D0A"), "J"); // LATIN LETTER SMALL CAPITAL J -- no decomposition - localTable.Add(ToUnichar("1D0B"), "K"); // LATIN LETTER SMALL CAPITAL K -- no decomposition - localTable.Add(ToUnichar("1D0C"), "L"); // LATIN LETTER SMALL CAPITAL L WITH STROKE -- no decomposition - localTable.Add(ToUnichar("1D0D"), "M"); // LATIN LETTER SMALL CAPITAL M -- no decomposition - localTable.Add(ToUnichar("1D0E"), "N"); // LATIN LETTER SMALL CAPITAL REVERSED N -- no decomposition - localTable.Add(ToUnichar("1D0F"), "O"); // LATIN LETTER SMALL CAPITAL O -- no decomposition - localTable.Add(ToUnichar("1D10"), "O"); // LATIN LETTER SMALL CAPITAL OPEN O -- no decomposition - localTable.Add(ToUnichar("1D11"), "o"); // LATIN SMALL LETTER SIDEWAYS O -- no decomposition - localTable.Add(ToUnichar("1D12"), "o"); // LATIN SMALL LETTER SIDEWAYS OPEN O -- no decomposition - localTable.Add(ToUnichar("1D13"), "o"); // LATIN SMALL LETTER SIDEWAYS O WITH STROKE -- no decomposition - localTable.Add(ToUnichar("1D14"), "oe"); // LATIN SMALL LETTER TURNED OE -- no decomposition - localTable.Add(ToUnichar("1D15"), "ou"); // LATIN LETTER SMALL CAPITAL OU -- no decomposition - localTable.Add(ToUnichar("1D16"), "o"); // LATIN SMALL LETTER TOP HALF O -- no decomposition - localTable.Add(ToUnichar("1D17"), "o"); // LATIN SMALL LETTER BOTTOM HALF O -- no decomposition - localTable.Add(ToUnichar("1D18"), "P"); // LATIN LETTER SMALL CAPITAL P -- no decomposition - localTable.Add(ToUnichar("1D19"), "R"); // LATIN LETTER SMALL CAPITAL REVERSED R -- no decomposition - localTable.Add(ToUnichar("1D1A"), "R"); // LATIN LETTER SMALL CAPITAL TURNED R -- no decomposition - localTable.Add(ToUnichar("1D1B"), "T"); // LATIN LETTER SMALL CAPITAL T -- no decomposition - localTable.Add(ToUnichar("1D1C"), "U"); // LATIN LETTER SMALL CAPITAL U -- no decomposition - localTable.Add(ToUnichar("1D1D"), "u"); // LATIN SMALL LETTER SIDEWAYS U -- no decomposition - localTable.Add(ToUnichar("1D1E"), "u"); // LATIN SMALL LETTER SIDEWAYS DIAERESIZED U -- no decomposition - localTable.Add(ToUnichar("1D1F"), "m"); // LATIN SMALL LETTER SIDEWAYS TURNED M -- no decomposition - localTable.Add(ToUnichar("1D20"), "V"); // LATIN LETTER SMALL CAPITAL V -- no decomposition - localTable.Add(ToUnichar("1D21"), "W"); // LATIN LETTER SMALL CAPITAL W -- no decomposition - localTable.Add(ToUnichar("1D22"), "Z"); // LATIN LETTER SMALL CAPITAL Z -- no decomposition - localTable.Add(ToUnichar("1D23"), "EZH"); // LATIN LETTER SMALL CAPITAL EZH -- no decomposition - localTable.Add(ToUnichar("1D24"), "'"); // LATIN LETTER VOICED LARYNGEAL SPIRANT -- no decomposition - localTable.Add(ToUnichar("1D25"), "L"); // LATIN LETTER AIN -- no decomposition - localTable.Add(ToUnichar("1D2C"), "A"); // MODIFIER LETTER CAPITAL A - localTable.Add(ToUnichar("1D2D"), "AE"); // MODIFIER LETTER CAPITAL AE - localTable.Add(ToUnichar("1D2E"), "B"); // MODIFIER LETTER CAPITAL B - localTable.Add(ToUnichar("1D2F"), "B"); // MODIFIER LETTER CAPITAL BARRED B -- no decomposition - localTable.Add(ToUnichar("1D30"), "D"); // MODIFIER LETTER CAPITAL D - localTable.Add(ToUnichar("1D31"), "E"); // MODIFIER LETTER CAPITAL E - localTable.Add(ToUnichar("1D32"), "E"); // MODIFIER LETTER CAPITAL REVERSED E - localTable.Add(ToUnichar("1D33"), "G"); // MODIFIER LETTER CAPITAL G - localTable.Add(ToUnichar("1D34"), "H"); // MODIFIER LETTER CAPITAL H - localTable.Add(ToUnichar("1D35"), "I"); // MODIFIER LETTER CAPITAL I - localTable.Add(ToUnichar("1D36"), "J"); // MODIFIER LETTER CAPITAL J - localTable.Add(ToUnichar("1D37"), "K"); // MODIFIER LETTER CAPITAL K - localTable.Add(ToUnichar("1D38"), "L"); // MODIFIER LETTER CAPITAL L - localTable.Add(ToUnichar("1D39"), "M"); // MODIFIER LETTER CAPITAL M - localTable.Add(ToUnichar("1D3A"), "N"); // MODIFIER LETTER CAPITAL N - localTable.Add(ToUnichar("1D3B"), "N"); // MODIFIER LETTER CAPITAL REVERSED N -- no decomposition - localTable.Add(ToUnichar("1D3C"), "O"); // MODIFIER LETTER CAPITAL O - localTable.Add(ToUnichar("1D3D"), "OU"); // MODIFIER LETTER CAPITAL OU - localTable.Add(ToUnichar("1D3E"), "P"); // MODIFIER LETTER CAPITAL P - localTable.Add(ToUnichar("1D3F"), "R"); // MODIFIER LETTER CAPITAL R - localTable.Add(ToUnichar("1D40"), "T"); // MODIFIER LETTER CAPITAL T - localTable.Add(ToUnichar("1D41"), "U"); // MODIFIER LETTER CAPITAL U - localTable.Add(ToUnichar("1D42"), "W"); // MODIFIER LETTER CAPITAL W - localTable.Add(ToUnichar("1D43"), "a"); // MODIFIER LETTER SMALL A - localTable.Add(ToUnichar("1D44"), "a"); // MODIFIER LETTER SMALL TURNED A - localTable.Add(ToUnichar("1D46"), "ae"); // MODIFIER LETTER SMALL TURNED AE - localTable.Add(ToUnichar("1D47"), "b"); // MODIFIER LETTER SMALL B - localTable.Add(ToUnichar("1D48"), "d"); // MODIFIER LETTER SMALL D - localTable.Add(ToUnichar("1D49"), "e"); // MODIFIER LETTER SMALL E - localTable.Add(ToUnichar("1D4A"), "e"); // MODIFIER LETTER SMALL SCHWA - localTable.Add(ToUnichar("1D4B"), "e"); // MODIFIER LETTER SMALL OPEN E - localTable.Add(ToUnichar("1D4C"), "e"); // MODIFIER LETTER SMALL TURNED OPEN E - localTable.Add(ToUnichar("1D4D"), "g"); // MODIFIER LETTER SMALL G - localTable.Add(ToUnichar("1D4E"), "i"); // MODIFIER LETTER SMALL TURNED I -- no decomposition - localTable.Add(ToUnichar("1D4F"), "k"); // MODIFIER LETTER SMALL K - localTable.Add(ToUnichar("1D50"), "m"); // MODIFIER LETTER SMALL M - localTable.Add(ToUnichar("1D51"), "g"); // MODIFIER LETTER SMALL ENG - localTable.Add(ToUnichar("1D52"), "o"); // MODIFIER LETTER SMALL O - localTable.Add(ToUnichar("1D53"), "o"); // MODIFIER LETTER SMALL OPEN O - localTable.Add(ToUnichar("1D54"), "o"); // MODIFIER LETTER SMALL TOP HALF O - localTable.Add(ToUnichar("1D55"), "o"); // MODIFIER LETTER SMALL BOTTOM HALF O - localTable.Add(ToUnichar("1D56"), "p"); // MODIFIER LETTER SMALL P - localTable.Add(ToUnichar("1D57"), "t"); // MODIFIER LETTER SMALL T - localTable.Add(ToUnichar("1D58"), "u"); // MODIFIER LETTER SMALL U - localTable.Add(ToUnichar("1D59"), "u"); // MODIFIER LETTER SMALL SIDEWAYS U - localTable.Add(ToUnichar("1D5A"), "m"); // MODIFIER LETTER SMALL TURNED M - localTable.Add(ToUnichar("1D5B"), "v"); // MODIFIER LETTER SMALL V - localTable.Add(ToUnichar("1D62"), "i"); // LATIN SUBSCRIPT SMALL LETTER I - localTable.Add(ToUnichar("1D63"), "r"); // LATIN SUBSCRIPT SMALL LETTER R - localTable.Add(ToUnichar("1D64"), "u"); // LATIN SUBSCRIPT SMALL LETTER U - localTable.Add(ToUnichar("1D65"), "v"); // LATIN SUBSCRIPT SMALL LETTER V - localTable.Add(ToUnichar("1D6B"), "ue"); // LATIN SMALL LETTER UE -- no decomposition - localTable.Add(ToUnichar("1E00"), "A"); // LATIN CAPITAL LETTER A WITH RING BELOW - localTable.Add(ToUnichar("1E01"), "a"); // LATIN SMALL LETTER A WITH RING BELOW - localTable.Add(ToUnichar("1E02"), "B"); // LATIN CAPITAL LETTER B WITH DOT ABOVE - localTable.Add(ToUnichar("1E03"), "b"); // LATIN SMALL LETTER B WITH DOT ABOVE - localTable.Add(ToUnichar("1E04"), "B"); // LATIN CAPITAL LETTER B WITH DOT BELOW - localTable.Add(ToUnichar("1E05"), "b"); // LATIN SMALL LETTER B WITH DOT BELOW - localTable.Add(ToUnichar("1E06"), "B"); // LATIN CAPITAL LETTER B WITH LINE BELOW - localTable.Add(ToUnichar("1E07"), "b"); // LATIN SMALL LETTER B WITH LINE BELOW - localTable.Add(ToUnichar("1E08"), "C"); // LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE - localTable.Add(ToUnichar("1E09"), "c"); // LATIN SMALL LETTER C WITH CEDILLA AND ACUTE - localTable.Add(ToUnichar("1E0A"), "D"); // LATIN CAPITAL LETTER D WITH DOT ABOVE - localTable.Add(ToUnichar("1E0B"), "d"); // LATIN SMALL LETTER D WITH DOT ABOVE - localTable.Add(ToUnichar("1E0C"), "D"); // LATIN CAPITAL LETTER D WITH DOT BELOW - localTable.Add(ToUnichar("1E0D"), "d"); // LATIN SMALL LETTER D WITH DOT BELOW - localTable.Add(ToUnichar("1E0E"), "D"); // LATIN CAPITAL LETTER D WITH LINE BELOW - localTable.Add(ToUnichar("1E0F"), "d"); // LATIN SMALL LETTER D WITH LINE BELOW - localTable.Add(ToUnichar("1E10"), "D"); // LATIN CAPITAL LETTER D WITH CEDILLA - localTable.Add(ToUnichar("1E11"), "d"); // LATIN SMALL LETTER D WITH CEDILLA - localTable.Add(ToUnichar("1E12"), "D"); // LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E13"), "d"); // LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E14"), "E"); // LATIN CAPITAL LETTER E WITH MACRON AND GRAVE - localTable.Add(ToUnichar("1E15"), "e"); // LATIN SMALL LETTER E WITH MACRON AND GRAVE - localTable.Add(ToUnichar("1E16"), "E"); // LATIN CAPITAL LETTER E WITH MACRON AND ACUTE - localTable.Add(ToUnichar("1E17"), "e"); // LATIN SMALL LETTER E WITH MACRON AND ACUTE - localTable.Add(ToUnichar("1E18"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E19"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E1A"), "E"); // LATIN CAPITAL LETTER E WITH TILDE BELOW - localTable.Add(ToUnichar("1E1B"), "e"); // LATIN SMALL LETTER E WITH TILDE BELOW - localTable.Add(ToUnichar("1E1C"), "E"); // LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE - localTable.Add(ToUnichar("1E1D"), "e"); // LATIN SMALL LETTER E WITH CEDILLA AND BREVE - localTable.Add(ToUnichar("1E1E"), "F"); // LATIN CAPITAL LETTER F WITH DOT ABOVE - localTable.Add(ToUnichar("1E1F"), "f"); // LATIN SMALL LETTER F WITH DOT ABOVE - localTable.Add(ToUnichar("1E20"), "G"); // LATIN CAPITAL LETTER G WITH MACRON - localTable.Add(ToUnichar("1E21"), "g"); // LATIN SMALL LETTER G WITH MACRON - localTable.Add(ToUnichar("1E22"), "H"); // LATIN CAPITAL LETTER H WITH DOT ABOVE - localTable.Add(ToUnichar("1E23"), "h"); // LATIN SMALL LETTER H WITH DOT ABOVE - localTable.Add(ToUnichar("1E24"), "H"); // LATIN CAPITAL LETTER H WITH DOT BELOW - localTable.Add(ToUnichar("1E25"), "h"); // LATIN SMALL LETTER H WITH DOT BELOW - localTable.Add(ToUnichar("1E26"), "H"); // LATIN CAPITAL LETTER H WITH DIAERESIS - localTable.Add(ToUnichar("1E27"), "h"); // LATIN SMALL LETTER H WITH DIAERESIS - localTable.Add(ToUnichar("1E28"), "H"); // LATIN CAPITAL LETTER H WITH CEDILLA - localTable.Add(ToUnichar("1E29"), "h"); // LATIN SMALL LETTER H WITH CEDILLA - localTable.Add(ToUnichar("1E2A"), "H"); // LATIN CAPITAL LETTER H WITH BREVE BELOW - localTable.Add(ToUnichar("1E2B"), "h"); // LATIN SMALL LETTER H WITH BREVE BELOW - localTable.Add(ToUnichar("1E2C"), "I"); // LATIN CAPITAL LETTER I WITH TILDE BELOW - localTable.Add(ToUnichar("1E2D"), "i"); // LATIN SMALL LETTER I WITH TILDE BELOW - localTable.Add(ToUnichar("1E2E"), "I"); // LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE - localTable.Add(ToUnichar("1E2F"), "i"); // LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE - localTable.Add(ToUnichar("1E30"), "K"); // LATIN CAPITAL LETTER K WITH ACUTE - localTable.Add(ToUnichar("1E31"), "k"); // LATIN SMALL LETTER K WITH ACUTE - localTable.Add(ToUnichar("1E32"), "K"); // LATIN CAPITAL LETTER K WITH DOT BELOW - localTable.Add(ToUnichar("1E33"), "k"); // LATIN SMALL LETTER K WITH DOT BELOW - localTable.Add(ToUnichar("1E34"), "K"); // LATIN CAPITAL LETTER K WITH LINE BELOW - localTable.Add(ToUnichar("1E35"), "k"); // LATIN SMALL LETTER K WITH LINE BELOW - localTable.Add(ToUnichar("1E36"), "L"); // LATIN CAPITAL LETTER L WITH DOT BELOW - localTable.Add(ToUnichar("1E37"), "l"); // LATIN SMALL LETTER L WITH DOT BELOW - localTable.Add(ToUnichar("1E38"), "L"); // LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON - localTable.Add(ToUnichar("1E39"), "l"); // LATIN SMALL LETTER L WITH DOT BELOW AND MACRON - localTable.Add(ToUnichar("1E3A"), "L"); // LATIN CAPITAL LETTER L WITH LINE BELOW - localTable.Add(ToUnichar("1E3B"), "l"); // LATIN SMALL LETTER L WITH LINE BELOW - localTable.Add(ToUnichar("1E3C"), "L"); // LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E3D"), "l"); // LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E3E"), "M"); // LATIN CAPITAL LETTER M WITH ACUTE - localTable.Add(ToUnichar("1E3F"), "m"); // LATIN SMALL LETTER M WITH ACUTE - localTable.Add(ToUnichar("1E40"), "M"); // LATIN CAPITAL LETTER M WITH DOT ABOVE - localTable.Add(ToUnichar("1E41"), "m"); // LATIN SMALL LETTER M WITH DOT ABOVE - localTable.Add(ToUnichar("1E42"), "M"); // LATIN CAPITAL LETTER M WITH DOT BELOW - localTable.Add(ToUnichar("1E43"), "m"); // LATIN SMALL LETTER M WITH DOT BELOW - localTable.Add(ToUnichar("1E44"), "N"); // LATIN CAPITAL LETTER N WITH DOT ABOVE - localTable.Add(ToUnichar("1E45"), "n"); // LATIN SMALL LETTER N WITH DOT ABOVE - localTable.Add(ToUnichar("1E46"), "N"); // LATIN CAPITAL LETTER N WITH DOT BELOW - localTable.Add(ToUnichar("1E47"), "n"); // LATIN SMALL LETTER N WITH DOT BELOW - localTable.Add(ToUnichar("1E48"), "N"); // LATIN CAPITAL LETTER N WITH LINE BELOW - localTable.Add(ToUnichar("1E49"), "n"); // LATIN SMALL LETTER N WITH LINE BELOW - localTable.Add(ToUnichar("1E4A"), "N"); // LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E4B"), "n"); // LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E4C"), "O"); // LATIN CAPITAL LETTER O WITH TILDE AND ACUTE - localTable.Add(ToUnichar("1E4D"), "o"); // LATIN SMALL LETTER O WITH TILDE AND ACUTE - localTable.Add(ToUnichar("1E4E"), "O"); // LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS - localTable.Add(ToUnichar("1E4F"), "o"); // LATIN SMALL LETTER O WITH TILDE AND DIAERESIS - localTable.Add(ToUnichar("1E50"), "O"); // LATIN CAPITAL LETTER O WITH MACRON AND GRAVE - localTable.Add(ToUnichar("1E51"), "o"); // LATIN SMALL LETTER O WITH MACRON AND GRAVE - localTable.Add(ToUnichar("1E52"), "O"); // LATIN CAPITAL LETTER O WITH MACRON AND ACUTE - localTable.Add(ToUnichar("1E53"), "o"); // LATIN SMALL LETTER O WITH MACRON AND ACUTE - localTable.Add(ToUnichar("1E54"), "P"); // LATIN CAPITAL LETTER P WITH ACUTE - localTable.Add(ToUnichar("1E55"), "p"); // LATIN SMALL LETTER P WITH ACUTE - localTable.Add(ToUnichar("1E56"), "P"); // LATIN CAPITAL LETTER P WITH DOT ABOVE - localTable.Add(ToUnichar("1E57"), "p"); // LATIN SMALL LETTER P WITH DOT ABOVE - localTable.Add(ToUnichar("1E58"), "R"); // LATIN CAPITAL LETTER R WITH DOT ABOVE - localTable.Add(ToUnichar("1E59"), "r"); // LATIN SMALL LETTER R WITH DOT ABOVE - localTable.Add(ToUnichar("1E5A"), "R"); // LATIN CAPITAL LETTER R WITH DOT BELOW - localTable.Add(ToUnichar("1E5B"), "r"); // LATIN SMALL LETTER R WITH DOT BELOW - localTable.Add(ToUnichar("1E5C"), "R"); // LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON - localTable.Add(ToUnichar("1E5D"), "r"); // LATIN SMALL LETTER R WITH DOT BELOW AND MACRON - localTable.Add(ToUnichar("1E5E"), "R"); // LATIN CAPITAL LETTER R WITH LINE BELOW - localTable.Add(ToUnichar("1E5F"), "r"); // LATIN SMALL LETTER R WITH LINE BELOW - localTable.Add(ToUnichar("1E60"), "S"); // LATIN CAPITAL LETTER S WITH DOT ABOVE - localTable.Add(ToUnichar("1E61"), "s"); // LATIN SMALL LETTER S WITH DOT ABOVE - localTable.Add(ToUnichar("1E62"), "S"); // LATIN CAPITAL LETTER S WITH DOT BELOW - localTable.Add(ToUnichar("1E63"), "s"); // LATIN SMALL LETTER S WITH DOT BELOW - localTable.Add(ToUnichar("1E64"), "S"); // LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE - localTable.Add(ToUnichar("1E65"), "s"); // LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE - localTable.Add(ToUnichar("1E66"), "S"); // LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE - localTable.Add(ToUnichar("1E67"), "s"); // LATIN SMALL LETTER S WITH CARON AND DOT ABOVE - localTable.Add(ToUnichar("1E68"), "S"); // LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE - localTable.Add(ToUnichar("1E69"), "s"); // LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE - localTable.Add(ToUnichar("1E6A"), "T"); // LATIN CAPITAL LETTER T WITH DOT ABOVE - localTable.Add(ToUnichar("1E6B"), "t"); // LATIN SMALL LETTER T WITH DOT ABOVE - localTable.Add(ToUnichar("1E6C"), "T"); // LATIN CAPITAL LETTER T WITH DOT BELOW - localTable.Add(ToUnichar("1E6D"), "t"); // LATIN SMALL LETTER T WITH DOT BELOW - localTable.Add(ToUnichar("1E6E"), "T"); // LATIN CAPITAL LETTER T WITH LINE BELOW - localTable.Add(ToUnichar("1E6F"), "t"); // LATIN SMALL LETTER T WITH LINE BELOW - localTable.Add(ToUnichar("1E70"), "T"); // LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E71"), "t"); // LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E72"), "U"); // LATIN CAPITAL LETTER U WITH DIAERESIS BELOW - localTable.Add(ToUnichar("1E73"), "u"); // LATIN SMALL LETTER U WITH DIAERESIS BELOW - localTable.Add(ToUnichar("1E74"), "U"); // LATIN CAPITAL LETTER U WITH TILDE BELOW - localTable.Add(ToUnichar("1E75"), "u"); // LATIN SMALL LETTER U WITH TILDE BELOW - localTable.Add(ToUnichar("1E76"), "U"); // LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E77"), "u"); // LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW - localTable.Add(ToUnichar("1E78"), "U"); // LATIN CAPITAL LETTER U WITH TILDE AND ACUTE - localTable.Add(ToUnichar("1E79"), "u"); // LATIN SMALL LETTER U WITH TILDE AND ACUTE - localTable.Add(ToUnichar("1E7A"), "U"); // LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS - localTable.Add(ToUnichar("1E7B"), "u"); // LATIN SMALL LETTER U WITH MACRON AND DIAERESIS - localTable.Add(ToUnichar("1E7C"), "V"); // LATIN CAPITAL LETTER V WITH TILDE - localTable.Add(ToUnichar("1E7D"), "v"); // LATIN SMALL LETTER V WITH TILDE - localTable.Add(ToUnichar("1E7E"), "V"); // LATIN CAPITAL LETTER V WITH DOT BELOW - localTable.Add(ToUnichar("1E7F"), "v"); // LATIN SMALL LETTER V WITH DOT BELOW - localTable.Add(ToUnichar("1E80"), "W"); // LATIN CAPITAL LETTER W WITH GRAVE - localTable.Add(ToUnichar("1E81"), "w"); // LATIN SMALL LETTER W WITH GRAVE - localTable.Add(ToUnichar("1E82"), "W"); // LATIN CAPITAL LETTER W WITH ACUTE - localTable.Add(ToUnichar("1E83"), "w"); // LATIN SMALL LETTER W WITH ACUTE - localTable.Add(ToUnichar("1E84"), "W"); // LATIN CAPITAL LETTER W WITH DIAERESIS - localTable.Add(ToUnichar("1E85"), "w"); // LATIN SMALL LETTER W WITH DIAERESIS - localTable.Add(ToUnichar("1E86"), "W"); // LATIN CAPITAL LETTER W WITH DOT ABOVE - localTable.Add(ToUnichar("1E87"), "w"); // LATIN SMALL LETTER W WITH DOT ABOVE - localTable.Add(ToUnichar("1E88"), "W"); // LATIN CAPITAL LETTER W WITH DOT BELOW - localTable.Add(ToUnichar("1E89"), "w"); // LATIN SMALL LETTER W WITH DOT BELOW - localTable.Add(ToUnichar("1E8A"), "X"); // LATIN CAPITAL LETTER X WITH DOT ABOVE - localTable.Add(ToUnichar("1E8B"), "x"); // LATIN SMALL LETTER X WITH DOT ABOVE - localTable.Add(ToUnichar("1E8C"), "X"); // LATIN CAPITAL LETTER X WITH DIAERESIS - localTable.Add(ToUnichar("1E8D"), "x"); // LATIN SMALL LETTER X WITH DIAERESIS - localTable.Add(ToUnichar("1E8E"), "Y"); // LATIN CAPITAL LETTER Y WITH DOT ABOVE - localTable.Add(ToUnichar("1E8F"), "y"); // LATIN SMALL LETTER Y WITH DOT ABOVE - localTable.Add(ToUnichar("1E90"), "Z"); // LATIN CAPITAL LETTER Z WITH CIRCUMFLEX - localTable.Add(ToUnichar("1E91"), "z"); // LATIN SMALL LETTER Z WITH CIRCUMFLEX - localTable.Add(ToUnichar("1E92"), "Z"); // LATIN CAPITAL LETTER Z WITH DOT BELOW - localTable.Add(ToUnichar("1E93"), "z"); // LATIN SMALL LETTER Z WITH DOT BELOW - localTable.Add(ToUnichar("1E94"), "Z"); // LATIN CAPITAL LETTER Z WITH LINE BELOW - localTable.Add(ToUnichar("1E95"), "z"); // LATIN SMALL LETTER Z WITH LINE BELOW - localTable.Add(ToUnichar("1E96"), "h"); // LATIN SMALL LETTER H WITH LINE BELOW - localTable.Add(ToUnichar("1E97"), "t"); // LATIN SMALL LETTER T WITH DIAERESIS - localTable.Add(ToUnichar("1E98"), "w"); // LATIN SMALL LETTER W WITH RING ABOVE - localTable.Add(ToUnichar("1E99"), "y"); // LATIN SMALL LETTER Y WITH RING ABOVE - localTable.Add(ToUnichar("1E9A"), "a"); // LATIN SMALL LETTER A WITH RIGHT HALF RING - localTable.Add(ToUnichar("1E9B"), "s"); // LATIN SMALL LETTER LONG S WITH DOT ABOVE - localTable.Add(ToUnichar("1EA0"), "A"); // LATIN CAPITAL LETTER A WITH DOT BELOW - localTable.Add(ToUnichar("1EA1"), "a"); // LATIN SMALL LETTER A WITH DOT BELOW - localTable.Add(ToUnichar("1EA2"), "A"); // LATIN CAPITAL LETTER A WITH HOOK ABOVE - localTable.Add(ToUnichar("1EA3"), "a"); // LATIN SMALL LETTER A WITH HOOK ABOVE - localTable.Add(ToUnichar("1EA4"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1EA5"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1EA6"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1EA7"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1EA8"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1EA9"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1EAA"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1EAB"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1EAC"), "A"); // LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1EAD"), "a"); // LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1EAE"), "A"); // LATIN CAPITAL LETTER A WITH BREVE AND ACUTE - localTable.Add(ToUnichar("1EAF"), "a"); // LATIN SMALL LETTER A WITH BREVE AND ACUTE - localTable.Add(ToUnichar("1EB0"), "A"); // LATIN CAPITAL LETTER A WITH BREVE AND GRAVE - localTable.Add(ToUnichar("1EB1"), "a"); // LATIN SMALL LETTER A WITH BREVE AND GRAVE - localTable.Add(ToUnichar("1EB2"), "A"); // LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE - localTable.Add(ToUnichar("1EB3"), "a"); // LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE - localTable.Add(ToUnichar("1EB4"), "A"); // LATIN CAPITAL LETTER A WITH BREVE AND TILDE - localTable.Add(ToUnichar("1EB5"), "a"); // LATIN SMALL LETTER A WITH BREVE AND TILDE - localTable.Add(ToUnichar("1EB6"), "A"); // LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW - localTable.Add(ToUnichar("1EB7"), "a"); // LATIN SMALL LETTER A WITH BREVE AND DOT BELOW - localTable.Add(ToUnichar("1EB8"), "E"); // LATIN CAPITAL LETTER E WITH DOT BELOW - localTable.Add(ToUnichar("1EB9"), "e"); // LATIN SMALL LETTER E WITH DOT BELOW - localTable.Add(ToUnichar("1EBA"), "E"); // LATIN CAPITAL LETTER E WITH HOOK ABOVE - localTable.Add(ToUnichar("1EBB"), "e"); // LATIN SMALL LETTER E WITH HOOK ABOVE - localTable.Add(ToUnichar("1EBC"), "E"); // LATIN CAPITAL LETTER E WITH TILDE - localTable.Add(ToUnichar("1EBD"), "e"); // LATIN SMALL LETTER E WITH TILDE - localTable.Add(ToUnichar("1EBE"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1EBF"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1EC0"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1EC1"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1EC2"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1EC3"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1EC4"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1EC5"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1EC6"), "E"); // LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1EC7"), "e"); // LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1EC8"), "I"); // LATIN CAPITAL LETTER I WITH HOOK ABOVE - localTable.Add(ToUnichar("1EC9"), "i"); // LATIN SMALL LETTER I WITH HOOK ABOVE - localTable.Add(ToUnichar("1ECA"), "I"); // LATIN CAPITAL LETTER I WITH DOT BELOW - localTable.Add(ToUnichar("1ECB"), "i"); // LATIN SMALL LETTER I WITH DOT BELOW - localTable.Add(ToUnichar("1ECC"), "O"); // LATIN CAPITAL LETTER O WITH DOT BELOW - localTable.Add(ToUnichar("1ECD"), "o"); // LATIN SMALL LETTER O WITH DOT BELOW - localTable.Add(ToUnichar("1ECE"), "O"); // LATIN CAPITAL LETTER O WITH HOOK ABOVE - localTable.Add(ToUnichar("1ECF"), "o"); // LATIN SMALL LETTER O WITH HOOK ABOVE - localTable.Add(ToUnichar("1ED0"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1ED1"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE - localTable.Add(ToUnichar("1ED2"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1ED3"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE - localTable.Add(ToUnichar("1ED4"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1ED5"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE - localTable.Add(ToUnichar("1ED6"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1ED7"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE - localTable.Add(ToUnichar("1ED8"), "O"); // LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1ED9"), "o"); // LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW - localTable.Add(ToUnichar("1EDA"), "O"); // LATIN CAPITAL LETTER O WITH HORN AND ACUTE - localTable.Add(ToUnichar("1EDB"), "o"); // LATIN SMALL LETTER O WITH HORN AND ACUTE - localTable.Add(ToUnichar("1EDC"), "O"); // LATIN CAPITAL LETTER O WITH HORN AND GRAVE - localTable.Add(ToUnichar("1EDD"), "o"); // LATIN SMALL LETTER O WITH HORN AND GRAVE - localTable.Add(ToUnichar("1EDE"), "O"); // LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE - localTable.Add(ToUnichar("1EDF"), "o"); // LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE - localTable.Add(ToUnichar("1EE0"), "O"); // LATIN CAPITAL LETTER O WITH HORN AND TILDE - localTable.Add(ToUnichar("1EE1"), "o"); // LATIN SMALL LETTER O WITH HORN AND TILDE - localTable.Add(ToUnichar("1EE2"), "O"); // LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW - localTable.Add(ToUnichar("1EE3"), "o"); // LATIN SMALL LETTER O WITH HORN AND DOT BELOW - localTable.Add(ToUnichar("1EE4"), "U"); // LATIN CAPITAL LETTER U WITH DOT BELOW - localTable.Add(ToUnichar("1EE5"), "u"); // LATIN SMALL LETTER U WITH DOT BELOW - localTable.Add(ToUnichar("1EE6"), "U"); // LATIN CAPITAL LETTER U WITH HOOK ABOVE - localTable.Add(ToUnichar("1EE7"), "u"); // LATIN SMALL LETTER U WITH HOOK ABOVE - localTable.Add(ToUnichar("1EE8"), "U"); // LATIN CAPITAL LETTER U WITH HORN AND ACUTE - localTable.Add(ToUnichar("1EE9"), "u"); // LATIN SMALL LETTER U WITH HORN AND ACUTE - localTable.Add(ToUnichar("1EEA"), "U"); // LATIN CAPITAL LETTER U WITH HORN AND GRAVE - localTable.Add(ToUnichar("1EEB"), "u"); // LATIN SMALL LETTER U WITH HORN AND GRAVE - localTable.Add(ToUnichar("1EEC"), "U"); // LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE - localTable.Add(ToUnichar("1EED"), "u"); // LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE - localTable.Add(ToUnichar("1EEE"), "U"); // LATIN CAPITAL LETTER U WITH HORN AND TILDE - localTable.Add(ToUnichar("1EEF"), "u"); // LATIN SMALL LETTER U WITH HORN AND TILDE - localTable.Add(ToUnichar("1EF0"), "U"); // LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW - localTable.Add(ToUnichar("1EF1"), "u"); // LATIN SMALL LETTER U WITH HORN AND DOT BELOW - localTable.Add(ToUnichar("1EF2"), "Y"); // LATIN CAPITAL LETTER Y WITH GRAVE - localTable.Add(ToUnichar("1EF3"), "y"); // LATIN SMALL LETTER Y WITH GRAVE - localTable.Add(ToUnichar("1EF4"), "Y"); // LATIN CAPITAL LETTER Y WITH DOT BELOW - localTable.Add(ToUnichar("1EF5"), "y"); // LATIN SMALL LETTER Y WITH DOT BELOW - localTable.Add(ToUnichar("1EF6"), "Y"); // LATIN CAPITAL LETTER Y WITH HOOK ABOVE - localTable.Add(ToUnichar("1EF7"), "y"); // LATIN SMALL LETTER Y WITH HOOK ABOVE - localTable.Add(ToUnichar("1EF8"), "Y"); // LATIN CAPITAL LETTER Y WITH TILDE - localTable.Add(ToUnichar("1EF9"), "y"); // LATIN SMALL LETTER Y WITH TILDE - localTable.Add(ToUnichar("2071"), "i"); // SUPERSCRIPT LATIN SMALL LETTER I - localTable.Add(ToUnichar("207F"), "n"); // SUPERSCRIPT LATIN SMALL LETTER N - localTable.Add(ToUnichar("212A"), "K"); // KELVIN SIGN - localTable.Add(ToUnichar("212B"), "A"); // ANGSTROM SIGN - localTable.Add(ToUnichar("212C"), "B"); // SCRIPT CAPITAL B - localTable.Add(ToUnichar("212D"), "C"); // BLACK-LETTER CAPITAL C - localTable.Add(ToUnichar("212F"), "e"); // SCRIPT SMALL E - localTable.Add(ToUnichar("2130"), "E"); // SCRIPT CAPITAL E - localTable.Add(ToUnichar("2131"), "F"); // SCRIPT CAPITAL F - localTable.Add(ToUnichar("2132"), "F"); // TURNED CAPITAL F -- no decomposition - localTable.Add(ToUnichar("2133"), "M"); // SCRIPT CAPITAL M - localTable.Add(ToUnichar("2134"), "0"); // SCRIPT SMALL O - localTable.Add(ToUnichar("213A"), "0"); // ROTATED CAPITAL Q -- no decomposition - localTable.Add(ToUnichar("2141"), "G"); // TURNED SANS-SERIF CAPITAL G -- no decomposition - localTable.Add(ToUnichar("2142"), "L"); // TURNED SANS-SERIF CAPITAL L -- no decomposition - localTable.Add(ToUnichar("2143"), "L"); // REVERSED SANS-SERIF CAPITAL L -- no decomposition - localTable.Add(ToUnichar("2144"), "Y"); // TURNED SANS-SERIF CAPITAL Y -- no decomposition - localTable.Add(ToUnichar("2145"), "D"); // DOUBLE-STRUCK ITALIC CAPITAL D - localTable.Add(ToUnichar("2146"), "d"); // DOUBLE-STRUCK ITALIC SMALL D - localTable.Add(ToUnichar("2147"), "e"); // DOUBLE-STRUCK ITALIC SMALL E - localTable.Add(ToUnichar("2148"), "i"); // DOUBLE-STRUCK ITALIC SMALL I - localTable.Add(ToUnichar("2149"), "j"); // DOUBLE-STRUCK ITALIC SMALL J - localTable.Add(ToUnichar("FB00"), "ff"); // LATIN SMALL LIGATURE FF - localTable.Add(ToUnichar("FB01"), "fi"); // LATIN SMALL LIGATURE FI - localTable.Add(ToUnichar("FB02"), "fl"); // LATIN SMALL LIGATURE FL - localTable.Add(ToUnichar("FB03"), "ffi"); // LATIN SMALL LIGATURE FFI - localTable.Add(ToUnichar("FB04"), "ffl"); // LATIN SMALL LIGATURE FFL - localTable.Add(ToUnichar("FB05"), "st"); // LATIN SMALL LIGATURE LONG S T - localTable.Add(ToUnichar("FB06"), "st"); // LATIN SMALL LIGATURE ST - localTable.Add(ToUnichar("FF21"), "A"); // FULLWIDTH LATIN CAPITAL LETTER B - localTable.Add(ToUnichar("FF22"), "B"); // FULLWIDTH LATIN CAPITAL LETTER B - localTable.Add(ToUnichar("FF23"), "C"); // FULLWIDTH LATIN CAPITAL LETTER C - localTable.Add(ToUnichar("FF24"), "D"); // FULLWIDTH LATIN CAPITAL LETTER D - localTable.Add(ToUnichar("FF25"), "E"); // FULLWIDTH LATIN CAPITAL LETTER E - localTable.Add(ToUnichar("FF26"), "F"); // FULLWIDTH LATIN CAPITAL LETTER F - localTable.Add(ToUnichar("FF27"), "G"); // FULLWIDTH LATIN CAPITAL LETTER G - localTable.Add(ToUnichar("FF28"), "H"); // FULLWIDTH LATIN CAPITAL LETTER H - localTable.Add(ToUnichar("FF29"), "I"); // FULLWIDTH LATIN CAPITAL LETTER I - localTable.Add(ToUnichar("FF2A"), "J"); // FULLWIDTH LATIN CAPITAL LETTER J - localTable.Add(ToUnichar("FF2B"), "K"); // FULLWIDTH LATIN CAPITAL LETTER K - localTable.Add(ToUnichar("FF2C"), "L"); // FULLWIDTH LATIN CAPITAL LETTER L - localTable.Add(ToUnichar("FF2D"), "M"); // FULLWIDTH LATIN CAPITAL LETTER M - localTable.Add(ToUnichar("FF2E"), "N"); // FULLWIDTH LATIN CAPITAL LETTER N - localTable.Add(ToUnichar("FF2F"), "O"); // FULLWIDTH LATIN CAPITAL LETTER O - localTable.Add(ToUnichar("FF30"), "P"); // FULLWIDTH LATIN CAPITAL LETTER P - localTable.Add(ToUnichar("FF31"), "Q"); // FULLWIDTH LATIN CAPITAL LETTER Q - localTable.Add(ToUnichar("FF32"), "R"); // FULLWIDTH LATIN CAPITAL LETTER R - localTable.Add(ToUnichar("FF33"), "S"); // FULLWIDTH LATIN CAPITAL LETTER S - localTable.Add(ToUnichar("FF34"), "T"); // FULLWIDTH LATIN CAPITAL LETTER T - localTable.Add(ToUnichar("FF35"), "U"); // FULLWIDTH LATIN CAPITAL LETTER U - localTable.Add(ToUnichar("FF36"), "V"); // FULLWIDTH LATIN CAPITAL LETTER V - localTable.Add(ToUnichar("FF37"), "W"); // FULLWIDTH LATIN CAPITAL LETTER W - localTable.Add(ToUnichar("FF38"), "X"); // FULLWIDTH LATIN CAPITAL LETTER X - localTable.Add(ToUnichar("FF39"), "Y"); // FULLWIDTH LATIN CAPITAL LETTER Y - localTable.Add(ToUnichar("FF3A"), "Z"); // FULLWIDTH LATIN CAPITAL LETTER Z - localTable.Add(ToUnichar("FF41"), "a"); // FULLWIDTH LATIN SMALL LETTER A - localTable.Add(ToUnichar("FF42"), "b"); // FULLWIDTH LATIN SMALL LETTER B - localTable.Add(ToUnichar("FF43"), "c"); // FULLWIDTH LATIN SMALL LETTER C - localTable.Add(ToUnichar("FF44"), "d"); // FULLWIDTH LATIN SMALL LETTER D - localTable.Add(ToUnichar("FF45"), "e"); // FULLWIDTH LATIN SMALL LETTER E - localTable.Add(ToUnichar("FF46"), "f"); // FULLWIDTH LATIN SMALL LETTER F - localTable.Add(ToUnichar("FF47"), "g"); // FULLWIDTH LATIN SMALL LETTER G - localTable.Add(ToUnichar("FF48"), "h"); // FULLWIDTH LATIN SMALL LETTER H - localTable.Add(ToUnichar("FF49"), "i"); // FULLWIDTH LATIN SMALL LETTER I - localTable.Add(ToUnichar("FF4A"), "j"); // FULLWIDTH LATIN SMALL LETTER J - localTable.Add(ToUnichar("FF4B"), "k"); // FULLWIDTH LATIN SMALL LETTER K - localTable.Add(ToUnichar("FF4C"), "l"); // FULLWIDTH LATIN SMALL LETTER L - localTable.Add(ToUnichar("FF4D"), "m"); // FULLWIDTH LATIN SMALL LETTER M - localTable.Add(ToUnichar("FF4E"), "n"); // FULLWIDTH LATIN SMALL LETTER N - localTable.Add(ToUnichar("FF4F"), "o"); // FULLWIDTH LATIN SMALL LETTER O - localTable.Add(ToUnichar("FF50"), "p"); // FULLWIDTH LATIN SMALL LETTER P - localTable.Add(ToUnichar("FF51"), "q"); // FULLWIDTH LATIN SMALL LETTER Q - localTable.Add(ToUnichar("FF52"), "r"); // FULLWIDTH LATIN SMALL LETTER R - localTable.Add(ToUnichar("FF53"), "s"); // FULLWIDTH LATIN SMALL LETTER S - localTable.Add(ToUnichar("FF54"), "t"); // FULLWIDTH LATIN SMALL LETTER T - localTable.Add(ToUnichar("FF55"), "u"); // FULLWIDTH LATIN SMALL LETTER U - localTable.Add(ToUnichar("FF56"), "v"); // FULLWIDTH LATIN SMALL LETTER V - localTable.Add(ToUnichar("FF57"), "w"); // FULLWIDTH LATIN SMALL LETTER W - localTable.Add(ToUnichar("FF58"), "x"); // FULLWIDTH LATIN SMALL LETTER X - localTable.Add(ToUnichar("FF59"), "y"); // FULLWIDTH LATIN SMALL LETTER Y - localTable.Add(ToUnichar("FF5A"), "z"); // FULLWIDTH LATIN SMALL LETTER Z - - _seoCharacterTable = localTable; - } - } - - } - - /// - /// Takes a hexadecimal string and converts it to an Unicode character - /// - /// A four-digit number in hex notation (eg, 00E7). - /// A unicode character, as string. - private static string ToUnichar(string hexString) - { - string returnChar; - byte[] b = new byte[2]; - var ue = new UnicodeEncoding(); - - // Take hexadecimal as text and make a Unicode char number - b[0] = Convert.ToByte(hexString.Substring(2, 2), 16); - b[1] = Convert.ToByte(hexString.Substring(0, 2), 16); - // Get the character the number represents - returnChar = ue.GetString(b); - return returnChar; - } } } diff --git a/src/Libraries/SmartStore.Core/Utilities/Threading/KeyedLock.cs b/src/Libraries/SmartStore.Core/Utilities/Threading/KeyedLock.cs new file mode 100644 index 0000000000..e08a6deb1c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Utilities/Threading/KeyedLock.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartStore.Utilities.Threading +{ + public sealed class KeyedLock + { + private static readonly TimeSpan _noTimeout = TimeSpan.FromMilliseconds(-1); + private static readonly ConcurrentDictionary _locks = new ConcurrentDictionary(); + + private int _waiterCount; + + private KeyedLock(object key) + { + Key = key; + Semaphore = new SemaphoreSlim(1, 1); + } + + private object Key { get; set; } + private SemaphoreSlim Semaphore { get; set; } + + public bool HasWaiters + { + get { return _waiterCount > 1; } + } + + public int WaiterCount + { + get { return _waiterCount; } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void IncrementCount() => Interlocked.Increment(ref _waiterCount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DecrementCount() => Interlocked.Decrement(ref _waiterCount); + + public static bool IsLockHeld(object key) + { + return _locks.ContainsKey(key); + } + + public static IDisposable Lock(object key) + { + return Lock(key, _noTimeout); + } + + public static IDisposable Lock(object key, TimeSpan timeout) + { + var keyedLock = GetOrCreateLock(key); + keyedLock.Semaphore.Wait(timeout); + return new Releaser(keyedLock); + } + + public static Task LockAsync(object key) + { + return LockAsync(key, _noTimeout, CancellationToken.None); + } + + public static Task LockAsync(object key, TimeSpan timeout) + { + return LockAsync(key, timeout, CancellationToken.None); + } + + public static Task LockAsync(object key, CancellationToken cancellationToken) + { + return LockAsync(key, _noTimeout, cancellationToken); + } + + public static Task LockAsync(object key, TimeSpan timeout, CancellationToken cancellationToken) + { + var keyedLock = GetOrCreateLock(key); + var wait = keyedLock.Semaphore.WaitAsync(timeout, cancellationToken); + + return wait.IsCompleted + ? Task.FromResult(new Releaser(keyedLock) as IDisposable) + : wait.ContinueWith((t, s) => new Releaser((KeyedLock)s) as IDisposable, + keyedLock, + CancellationToken.None, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + } + + private static KeyedLock GetOrCreateLock(object key) + { + Guard.NotNull(key, nameof(key)); + + var item = _locks.GetOrAdd(key, k => new KeyedLock(key)); + item.IncrementCount(); + return item; + } + + sealed class Releaser : IDisposable + { + private KeyedLock _item; + + public Releaser(KeyedLock item) + { + _item = item; + } + + public void Dispose() + { + _item.DecrementCount(); + if (_item.WaiterCount == 0) + { + // Remove from dict + _locks.TryRemove(_item.Key, out _); + } + + _item.Semaphore.Release(); + _item = null; + } + } + } +} diff --git a/src/Libraries/SmartStore.Core/WebHelper.cs b/src/Libraries/SmartStore.Core/WebHelper.cs index 4d6c93c601..3a94e1a417 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,25 +42,18 @@ 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.SafeGetHttpRequest()?.UrlReferrer?.ToString() ?? string.Empty; } public virtual string GetClientIdent() { var ipAddress = this.GetCurrentIpAddress(); - var userAgent = _httpContext.Request?.UserAgent.EmptyNull(); + var userAgent = _httpContext.SafeGetHttpRequest()?.UserAgent.EmptyNull(); if (ipAddress.HasValue() && userAgent.HasValue()) { @@ -77,12 +70,13 @@ public virtual string GetCurrentIpAddress() return _ipAddress; } - if (_httpContext == null && _httpContext.Request == null) + var httpRequest = _httpContext.SafeGetHttpRequest(); + if (httpRequest == null) { return string.Empty; } - var vars = _httpContext.Request.ServerVariables; + var vars = httpRequest.ServerVariables; var keysToCheck = new string[] { @@ -97,29 +91,43 @@ public virtual string GetCurrentIpAddress() }; string result = null; + IPAddress ipv6 = null; foreach (var key in keysToCheck) { var ipString = vars[key]; - if (ipString.HasValue()) + + if (!string.IsNullOrEmpty(ipString)) { var arrStrings = ipString.Split(','); - // Take the last entry - ipString = arrStrings[arrStrings.Length - 1].Trim(); - IPAddress address; - if (IPAddress.TryParse(ipString, out address)) - { - result = ipString; - break; - } + // Iterate list from end to start (IPv6 addresses usually have precedence) + for (int i = arrStrings.Length - 1; i >= 0; i--) + { + ipString = arrStrings[i].Trim(); + + if (IPAddress.TryParse(ipString, out var address)) + { + if (address.AddressFamily == AddressFamily.InterNetworkV6) + { + ipv6 = address; + } + else + { + result = ipString; + break; + } + } + } } } - if (result == "::1") - { - result = "127.0.0.1"; - } + if (string.IsNullOrEmpty(result) && ipv6 != null) + { + result = ipv6.ToString() == "::1" + ? "127.0.0.1" + : ipv6.MapToIPv4().ToString(); + } return (_ipAddress = result.EmptyNull()); } @@ -133,36 +141,37 @@ public virtual string GetThisPageUrl(bool includeQueryString) public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl) { string url = string.Empty; - if (_httpContext == null || _httpContext.Request == null) + var httpRequest = _httpContext.SafeGetHttpRequest(); + + if (httpRequest == null) return url; if (includeQueryString) { - bool appPathPossiblyAppended; - string storeHost = GetStoreHost(useSsl, out appPathPossiblyAppended).TrimEnd('/'); + string storeHost = GetStoreHost(useSsl, out bool appPathPossiblyAppended).TrimEnd('/'); - string rawUrl; + string rawUrl; if (appPathPossiblyAppended) { - rawUrl = _httpContext.Request.AppRelativeCurrentExecutionFilePath.TrimStart('~'); + rawUrl = httpRequest.AppRelativeCurrentExecutionFilePath.TrimStart('~'); - if (_httpContext.Request.Url != null && _httpContext.Request.Url.Query != null) + if (httpRequest.Url != null && httpRequest.Url.Query != null) { - rawUrl += _httpContext.Request.Url.Query; + rawUrl += httpRequest.Url.Query; } } else { - rawUrl = _httpContext.Request.RawUrl; + rawUrl = httpRequest.RawUrl; } url = storeHost + rawUrl; } else { - if (_httpContext.Request.Url != null) + if (httpRequest.Url != null) { - url = _httpContext.Request.Url.GetLeftPart(UriPartial.Path); + url = httpRequest.Url.GetLeftPart(UriPartial.Path); } } @@ -174,9 +183,10 @@ public virtual bool IsCurrentConnectionSecured() if (!_isCurrentConnectionSecured.HasValue) { _isCurrentConnectionSecured = false; - if (_httpContext?.Request != null) + var httpRequest = _httpContext.SafeGetHttpRequest(); + if (httpRequest != null) { - _isCurrentConnectionSecured = _httpContext.Request.IsSecureConnection(); + _isCurrentConnectionSecured = httpRequest.IsSecureConnection(); } } @@ -185,23 +195,7 @@ public virtual bool IsCurrentConnectionSecured() public virtual string ServerVariables(string name) { - string result = string.Empty; - - try - { - if (_httpContext != null && _httpContext.Request != null) - { - if (_httpContext.Request.ServerVariables[name] != null) - { - result = _httpContext.Request.ServerVariables[name]; - } - } - } - catch - { - result = string.Empty; - } - return result; + return _httpContext.SafeGetHttpRequest()?.ServerVariables[name].EmptyNull(); } [SuppressMessage("ReSharper", "UnusedMember.Local")] @@ -246,8 +240,7 @@ private string GetStoreHost(bool useSsl, out bool appPathPossiblyAppended) if (_currentStore == null) { - IStoreContext storeContext; - if (EngineContext.Current.ContainerManager.TryResolve(null, out storeContext)) // Unit test safe! + if (EngineContext.Current.ContainerManager.TryResolve(null, out IStoreContext storeContext)) // Unit test safe! { _currentStore = storeContext.CurrentStore; if (_currentStore == null) @@ -330,19 +323,18 @@ public virtual string GetStoreLocation() public virtual string GetStoreLocation(bool useSsl) { - //return HostingEnvironment.ApplicationVirtualPath; - - bool appPathPossiblyAppended; - string result = GetStoreHost(useSsl, out appPathPossiblyAppended); + string result = GetStoreHost(useSsl, out var appPathPossiblyAppended); if (result.EndsWith("/")) { result = result.Substring(0, result.Length - 1); } - if ( _httpContext?.Request != null) + var httpRequest = _httpContext.SafeGetHttpRequest(); + + if (httpRequest != null) { - var appPath = _httpContext.Request.ApplicationPath; + var appPath = httpRequest.ApplicationPath; if (!appPathPossiblyAppended && !result.EndsWith(appPath, StringComparison.OrdinalIgnoreCase)) { // in a shared ssl scenario the user defined https url could contain @@ -431,13 +423,12 @@ public virtual string RemoveQueryString(string url, string queryString) public virtual T QueryString(string name) { - string queryParam = null; - - if (_httpContext != null && _httpContext.Request.QueryString[name] != null) - queryParam = _httpContext.Request.QueryString[name]; + var queryParam = _httpContext.SafeGetHttpRequest()?.QueryString[name]; - if (!String.IsNullOrEmpty(queryParam)) - return queryParam.Convert(); + if (!string.IsNullOrEmpty(queryParam)) + { + return queryParam.Convert(); + } return default(T); } @@ -506,10 +497,7 @@ private void DeleteMvcTypeCacheFiles() File.Delete(Path.Combine(userCacheDir, "MVC-ControllerTypeCache.xml")); File.Delete(Path.Combine(userCacheDir, "MVC-AreaRegistrationTypeCache.xml")); } - catch - { - - } + catch { } } private bool TryWriteBinFolder() @@ -619,7 +607,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) => { @@ -636,8 +624,9 @@ public static string MakeAllUrlsAbsolute(string html, string protocol, string ho /// /// Prepends protocol and host to the given (relative) url /// + /// Changes the protocol if passed. [SuppressMessage("ReSharper", "AccessToModifiedClosure")] - public static string GetAbsoluteUrl(string url, HttpRequestBase request) + public static string GetAbsoluteUrl(string url, HttpRequestBase request, bool enforceScheme = false, string protocol = null) { Guard.NotEmpty(url, nameof(url)); Guard.NotNull(request, nameof(request)); @@ -647,17 +636,26 @@ 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; } + protocol = protocol ?? request.Url.Scheme; + + if (url.StartsWith("//")) + { + return enforceScheme + ? String.Concat(protocol, ":", url) + : url; + } + if (url.StartsWith("~")) { url = VirtualPathUtility.ToAbsolute(url); } - url = string.Format("{0}://{1}{2}", request.Url.Scheme, request.Url.Authority, url); + url = string.Format("{0}://{1}{2}", protocol, request.Url.Authority, url); return url; } diff --git a/src/Libraries/SmartStore.Core/app.config b/src/Libraries/SmartStore.Core/app.config index 8c178073c2..f9acd6487c 100644 --- a/src/Libraries/SmartStore.Core/app.config +++ b/src/Libraries/SmartStore.Core/app.config @@ -4,16 +4,20 @@ - + - + + + + + diff --git a/src/Libraries/SmartStore.Core/packages.config b/src/Libraries/SmartStore.Core/packages.config index 4820921437..19d5cd9737 100644 --- a/src/Libraries/SmartStore.Core/packages.config +++ b/src/Libraries/SmartStore.Core/packages.config @@ -1,18 +1,19 @@  - - + + + - - - + + + - - + + - + \ No newline at end of file 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/DbCacheExtensions.cs b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs index a53b2597af..e055db6a8c 100644 --- a/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs +++ b/src/Libraries/SmartStore.Data/Caching/DbCacheExtensions.cs @@ -115,9 +115,8 @@ internal static DbCacheEntry GetCacheEntry(this IQueryable source, string { return null; } - - DbCacheEntry entry; - if (!cache.RequestTryGet(cacheKey.Key, out entry)) + + if (!cache.RequestTryGet(cacheKey.Key, out var entry)) { entry = cache.RequestPut(cacheKey.Key, valueFactory(), cacheKey.AffectedEntitySets); } @@ -196,73 +195,52 @@ public static IQueryable Cached(this IQueryable source) private class CacheKey { - private readonly IQueryable _source; - private string _key; - private string[] _affectedEntitySets; - private ObjectQuery _objectQuery; - private bool _objectQueryResolved; - public CacheKey(IQueryable source, string customKey = null) { Guard.NotNull(source, nameof(source)); - _source = source; - _key = customKey.NullEmpty(); + GenerateKeyAndAffectedEntitySets(source, customKey); } - private void EnsureObjectQuery() + private void GenerateKeyAndAffectedEntitySets(IQueryable source, string customKey = null) { - if (!_objectQueryResolved && _objectQuery == null) - { - _objectQuery = DbCacheUtil.GetObjectQuery(_source) ?? _source as ObjectQuery; - _objectQueryResolved = true; - } - } + var objectQuery = DbCacheUtil.GetObjectQuery(source) ?? source as ObjectQuery; - public string Key - { - get + var key = customKey.NullEmpty(); + if (objectQuery != null && string.IsNullOrEmpty(key)) { - if (_key == null && !_objectQueryResolved) + var commandInfo = objectQuery.GetCommandInfo(); + + var sb = new StringBuilder(); + sb.AppendLine(commandInfo.Sql); + + foreach (DbParameter parameter in commandInfo.Parameters) { - EnsureObjectQuery(); - if (_objectQuery != null) - { - var commandInfo = _objectQuery.GetCommandInfo(); - - var sb = new StringBuilder(); - sb.AppendLine(commandInfo.Sql); - - foreach (DbParameter parameter in commandInfo.Parameters) - { - sb.Append(parameter.ParameterName); - sb.Append(";"); - sb.Append(parameter.Value); - sb.AppendLine(";"); - } - - _key = sb.ToString(); - } + sb.Append(parameter.ParameterName); + sb.Append(";"); + sb.Append(parameter.Value); + sb.AppendLine(";"); } - return _key; + key = sb.ToString(); } + + Key = key; + AffectedEntitySets = objectQuery != null + ? objectQuery.GetAffectedEntitySets() + : new string[0]; } - public string[] AffectedEntitySets + public string Key { - get - { - if (_affectedEntitySets == null) - { - EnsureObjectQuery(); - _affectedEntitySets = _objectQuery != null - ? _objectQuery.GetAffectedEntitySets() - : new string[0]; - } + get; + private set; + } - return _affectedEntitySets; - } + public string[] AffectedEntitySets + { + get; + private set; } } } 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 ea10af6d3b..67d4561746 100644 --- a/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs +++ b/src/Libraries/SmartStore.Data/Caching/EfDbCache.cs @@ -3,12 +3,12 @@ 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; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Utilities.Threading; namespace SmartStore.Data.Caching { @@ -18,12 +18,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; @@ -192,13 +193,13 @@ public virtual bool TryGet(string key, out object value) key = HashKey(key); var now = DateTime.UtcNow; - var entry = _cache.Get(key); + var entry = _cache.Get(key, independent: true); if (entry != null) { if (entry.HasExpired(now)) { - lock (String.Intern(key)) + using (KeyedLock.Lock(key)) { InvalidateItemUnlocked(entry); } @@ -222,7 +223,7 @@ public virtual DbCacheEntry Put(string key, object value, IEnumerable de key = HashKey(key); - lock (String.Intern(key)) + using (KeyedLock.Lock(key)) { var entitySets = dependentEntitySets.Distinct().ToArray(); var entry = new DbCacheEntry @@ -248,8 +249,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) @@ -290,7 +291,7 @@ public virtual void InvalidateItem(string key) Guard.NotEmpty(key, nameof(key)); - lock (String.Intern(key)) + using (KeyedLock.Lock(key)) { InvalidateItemUnlocked(key); } @@ -305,7 +306,7 @@ private void InvalidateItemUnlocked(string key) if (_cache.Contains(key)) { - var entry = _cache.Get(key); + var entry = _cache.Get(key, true); if (entry != null) { InvalidateItemUnlocked(entry); diff --git a/src/Libraries/SmartStore.Data/Caching/EfDbModelStore.cs b/src/Libraries/SmartStore.Data/Caching/EfDbModelStore.cs new file mode 100644 index 0000000000..393becfbf3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Caching/EfDbModelStore.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity.Infrastructure; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using SmartStore.Core.Plugins; +using SmartStore.Utilities; + +namespace SmartStore.Data.Caching +{ + public sealed class EfDbModelStore : DefaultDbModelStore + { + public EfDbModelStore(string location) + : base(location) + { + } + + public override DbCompiledModel TryLoad(Type contextType) + { + string path = GetFilePath(contextType); + + if (File.Exists(path)) + { + var cachedModelCreatedOn = File.GetLastWriteTimeUtc(path); + var localAssemblyFile = FindLocalAssemblyFile(contextType.Assembly); + if (localAssemblyFile == null || !localAssemblyFile.Exists || localAssemblyFile.LastWriteTimeUtc > cachedModelCreatedOn) + { + File.Delete(path); + Debug.WriteLine("Cached db model obsolete. Re-creating cached db model edmx."); + } + } + else + { + Debug.WriteLine("No cached db model found. Creating cached db model edmx."); + } + + return base.TryLoad(contextType); + } + + private FileInfo FindLocalAssemblyFile(Assembly assembly) + { + // ASP.NET loads assemblies from its dynamic temp folder, + // where DLLs get redeployed on every app startup, which + // makes it impossible for us to check for updated DLLs. + // Therefore we'll find passed assembly in the app folder for datetime checking. + var file = new FileInfo(assembly.Location); + + if (file.Name.IsCaseInsensitiveEqual("SmartStore.Data.dll")) + { + return new FileInfo(Path.Combine(CommonHelper.MapPath("~/bin"), file.Name)); + } + + var pluginSystemName = Path.GetFileNameWithoutExtension(file.Name); + var pluginDescriptor = PluginFinder.Current.GetPluginDescriptorBySystemName(pluginSystemName, false); + + return pluginDescriptor?.Assembly?.OriginalFile; + } + } +} diff --git a/src/Libraries/SmartStore.Data/Caching/EfMappingViewCache.cs b/src/Libraries/SmartStore.Data/Caching/EfMappingViewCache.cs new file mode 100644 index 0000000000..80720306ed --- /dev/null +++ b/src/Libraries/SmartStore.Data/Caching/EfMappingViewCache.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Data.Entity.Infrastructure.MappingViews; +using System.Data.Entity.Core.Metadata.Edm; + +namespace SmartStore.Data.Caching +{ + public class EfMappingViewCache : DbMappingViewCache + { + private readonly string _hash; + private readonly Dictionary _views; + + public EfMappingViewCache(EfMappingView cachedView) + { + _hash = cachedView.Hash; + _views = cachedView.Views; + } + + public EfMappingViewCache(string hash, Dictionary views) + { + Guard.NotEmpty(hash, nameof(hash)); + Guard.NotNull(views, nameof(views)); + + _hash = hash; + _views = views.ToDictionary(kvp => GetExtentFullName(kvp.Key), kvp => kvp.Value); + } + + public override string MappingHashValue + { + get { return _hash; } + } + + public override DbMappingView GetView(EntitySetBase extent) + { + _views.TryGetValue(GetExtentFullName(extent), out var mappingView); + return mappingView; + } + + internal static string GetExtentFullName(EntitySetBase entitySet) + { + return entitySet.EntityContainer.Name + "." + entitySet.Name; + } + } + + /// + /// For JSON serialization + /// + public sealed class EfMappingView + { + public string Hash { get; set; } + public Dictionary Views { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Data/Caching/EfMappingViewCacheFactory.cs b/src/Libraries/SmartStore.Data/Caching/EfMappingViewCacheFactory.cs new file mode 100644 index 0000000000..021da40be7 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Caching/EfMappingViewCacheFactory.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data.Entity; +using System.Data.Entity.Core.Mapping; +using System.Data.Entity.Core.Metadata.Edm; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.Infrastructure.MappingViews; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using SmartStore.Utilities; + +namespace SmartStore.Data.Caching +{ + public class EfMappingViewCacheFactory : DbMappingViewCacheFactory + { + private readonly static ConcurrentDictionary _lookup = + new ConcurrentDictionary(); + + private readonly string _viewFilePath; + private readonly static object _lock = new object(); + + private EfMappingViewCacheFactory(string viewFilePath) + { + _viewFilePath = viewFilePath; + } + + public static void SetContext() + where TContext : DbContext, new() + { + using (var ctx = new TContext()) + { + SetContext(ctx); + } + } + + /// + /// Sets a for a mapping represented + /// by a derived class. + /// + /// A derived class instance containing + /// mapping to set the view cache factory for. + /// View cache factory + /// + /// This method must be called before EntityFramework generates views for the mapping + /// (which typically happens on the first query). + /// cannot be set more than once for the same + /// . + /// + public static void SetContext(DbContext ctx) + { + Guard.NotNull(ctx, nameof(ctx)); + + var objectContext = ((IObjectContextAdapter)ctx).ObjectContext; + var itemCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); + var type = ctx.GetType(); + var filePath = Path.Combine(CommonHelper.MapPath("~/App_Data/EfCache"), type.Namespace + "." + type.Name + ".views"); + var factory = new EfMappingViewCacheFactory(filePath); + + itemCollection.MappingViewCacheFactory = factory; + _lookup.TryAdd(factory, itemCollection); + } + + /// + /// Returns a instance for the . + /// + /// View cache factory to return for. + /// A instance for the . + public static StorageMappingItemCollection GetMappingItemCollection(DbMappingViewCacheFactory viewCacheFactory) + { + Guard.NotNull(viewCacheFactory, nameof(viewCacheFactory)); + + if (!_lookup.TryGetValue(viewCacheFactory, out var mappingItemCollection)) + { + throw new InvalidOperationException("No StorageMappingItemCollection instance found for the provided DbMappingViewCacheFactory."); + } + + return mappingItemCollection; + } + + /// + /// Creates a new instance of the class for + /// the given and . + /// + /// The name of the conceptual model container. + /// The name of the store model container. + /// A new instance of the class. + public override DbMappingViewCache Create(string conceptualModelContainerName, string storeModelContainerName) + { + var mappingItemCollection = GetMappingItemCollection(); + + if (mappingItemCollection == null) + { + throw new InvalidOperationException("View cache not set for this mapping item collection"); + } + + var hash = mappingItemCollection.ComputeMappingHashValue(conceptualModelContainerName, storeModelContainerName); + + var cachedViews = Load(); + if (cachedViews != null && cachedViews.Hash == hash) + { + return new EfMappingViewCache(cachedViews); + } + + var views = GenerateViews(mappingItemCollection, conceptualModelContainerName, storeModelContainerName); + + cachedViews = CreateCachedViews(hash, views); + Save(cachedViews); + + return new EfMappingViewCache(hash, views); + } + + // virtual for mocking + internal virtual StorageMappingItemCollection GetMappingItemCollection() + { + return GetMappingItemCollection(this); + } + + // virtual for mocking + internal virtual Dictionary GenerateViews( + StorageMappingItemCollection mappingItemCollection, + string conceptualModelContainerName, + string storeModelContainerName) + { + var errors = new List(); + return mappingItemCollection.GenerateViews(conceptualModelContainerName, storeModelContainerName, errors); + } + + private EfMappingView CreateCachedViews(string hash, Dictionary views) + { + return new EfMappingView + { + Hash = hash, + Views = views.ToDictionary(k => EfMappingViewCache.GetExtentFullName(k.Key), v => v.Value) + }; + } + + private EfMappingView Load() + { + try + { + lock (_lock) + { + if (File.Exists(_viewFilePath)) + { + var json = File.ReadAllText(_viewFilePath); + return JsonConvert.DeserializeObject(json); + } + } + } + catch { } + + return null; + } + + private void Save(EfMappingView view) + { + try + { + lock (_lock) + { + var json = JsonConvert.SerializeObject(view); + File.WriteAllText(_viewFilePath, json); + } + } + catch { } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Caching/IDbCache.cs b/src/Libraries/SmartStore.Data/Caching/IDbCache.cs index 3ca2c570fb..086fdf447c 100644 --- a/src/Libraries/SmartStore.Data/Caching/IDbCache.cs +++ b/src/Libraries/SmartStore.Data/Caching/IDbCache.cs @@ -77,7 +77,7 @@ public interface IDbCache void RequestInvalidateItem(string key); } - internal class NullDbCache : IDbCache + public class NullDbCache : IDbCache { public bool Enabled { diff --git a/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs index 4e7437f334..bf528fd1e0 100644 --- a/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs +++ b/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs @@ -3,6 +3,7 @@ using System.Data.Entity.Infrastructure; using SmartStore.Core; using System.Diagnostics; +using SmartStore.Core.Data; namespace SmartStore { @@ -92,7 +93,36 @@ public static void DumpAttachedEntities(this DbContext context) .ForEach(x => "{0} {1} {2}".FormatInvariant((x.Entity as BaseEntity).Id, x.State.ToString(), x.Entity.GetType().Name).Dump()); } - internal static SqlServerInfo GetSqlServerInfo(this DbContext context) + [Conditional("DEBUG")] + [DebuggerStepThrough] + public static void CreateSqlTimeout(this DbContext context) + { + var timeoutErrorSql = + "CREATE PROCEDURE [dbo].[GetTimeoutError]\r\n" + + "AS\r\n" + + "BEGIN\r\n" + + " WAITFOR DELAY '00:01:00'\r\n" + + " SELECT GETDATE()\r\n" + + "END"; + + var storedProcedureSql = + "IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[GetTimeoutError]') AND OBJECTPROPERTY(object_id,N'IsProcedure') = 1)" + + "BEGIN EXEC('" + timeoutErrorSql.Replace("'", "''") + "') END;"; + + context.Execute(storedProcedureSql); + + var connectionString = DataSettings.Current.DataConnectionString; + using (var cmd = new System.Data.SqlClient.SqlCommand("GetTimeoutError", new System.Data.SqlClient.SqlConnection(connectionString))) + { + cmd.CommandType = System.Data.CommandType.StoredProcedure; + cmd.CommandTimeout = 30; // Seconds. + + cmd.Connection.Open(); + cmd.ExecuteNonQuery(); + } + } + + internal static SqlServerInfo GetSqlServerInfo(this DbContext context) { string sql = @"SELECT SERVERPROPERTY('productversion') as 'ProductVersion', 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/CategoryMap.cs b/src/Libraries/SmartStore.Data/Mapping/Catalog/CategoryMap.cs index 4920d45081..c69a6f88d4 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Catalog/CategoryMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Catalog/CategoryMap.cs @@ -12,7 +12,8 @@ public CategoryMap() this.Property(c => c.Name).IsRequired().HasMaxLength(400); this.Property(c => c.FullName).HasMaxLength(400); this.Property(c => c.BottomDescription).IsMaxLength(); - this.Property(c => c.Description).IsMaxLength(); + this.Property(c => c.ExternalLink).HasMaxLength(255).IsOptional(); + this.Property(c => c.Description).IsMaxLength(); this.Property(c => c.MetaKeywords).HasMaxLength(400); this.Property(c => c.MetaTitle).HasMaxLength(400); this.Property(c => c.PageSizeOptions).HasMaxLength(200).IsOptional(); 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/Directory/QuantityUnitMap.cs b/src/Libraries/SmartStore.Data/Mapping/Directory/QuantityUnitMap.cs index 7bd783bcad..a840571e67 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Directory/QuantityUnitMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Directory/QuantityUnitMap.cs @@ -7,12 +7,13 @@ public partial class QuantityUnitMap : EntityTypeConfiguration { public QuantityUnitMap() { - this.ToTable("QuantityUnit"); - this.HasKey(c => c.Id); - this.Property(c => c.Name).IsRequired().HasMaxLength(50); - this.Property(c => c.Description).HasMaxLength(50); - this.Property(c => c.DisplayLocale).HasMaxLength(50); - this.Property(c => c.DisplayOrder); + ToTable("QuantityUnit"); + HasKey(c => c.Id); + Property(c => c.Name).IsRequired().HasMaxLength(50); + Property(c => c.NamePlural).IsRequired().HasMaxLength(50); + Property(c => c.Description).HasMaxLength(50); + Property(c => c.DisplayLocale).HasMaxLength(50); + Property(c => c.DisplayOrder); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs new file mode 100644 index 0000000000..40d9e9d8bb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Mapping/Forums/ForumPostVoteMap.cs @@ -0,0 +1,18 @@ +using System.Data.Entity.ModelConfiguration; +using SmartStore.Core.Domain.Forums; + +namespace SmartStore.Data.Mapping.Forums +{ + public partial class ForumPostVoteMap : EntityTypeConfiguration + { + public ForumPostVoteMap() + { + ToTable("ForumPostVote"); + + HasRequired(fpl => fpl.ForumPost) + .WithMany(fp => fp.ForumPostVotes) + .HasForeignKey(fpl => fpl.ForumPostId) + .WillCascadeOnDelete(true); + } + } +} 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..817cabd4e6 --- /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).IsRequired().HasMaxLength(400); + Property(x => x.Error); + 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", "Zurck 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/201608110948010_MediaStorageData.cs b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs index e254602331..1cb4b063e3 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201608110948010_MediaStorageData.cs @@ -123,80 +123,83 @@ public void Seed(SmartObjectContext context) context.SaveChanges(); } - #region Pictures - - if (storeMediaInDb) + using (var scope = new DbContextScope(context, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { - PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), picture => - { + #region Pictures - if (picture.PictureBinary != null && picture.PictureBinary.LongLength > 0) + if (storeMediaInDb) + { + PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), picture => { - picture.MediaStorage = new MediaStorage { Data = picture.PictureBinary }; - picture.PictureBinary = null; - } - }); - } - #endregion + if (picture.PictureBinary != null && picture.PictureBinary.LongLength > 0) + { + picture.MediaStorage = new MediaStorage { Data = picture.PictureBinary }; + picture.PictureBinary = null; + } + }); + } - #region Downloads + #endregion - PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), download => - { - if (download.DownloadBinary != null && download.DownloadBinary.LongLength > 0) + #region Downloads + + PageEntities(context, mediaStorages, context.Set().OrderBy(x => x.Id), download => { - if (storeMediaInDb) + if (download.DownloadBinary != null && download.DownloadBinary.LongLength > 0) { - // move binary data - download.MediaStorage = new MediaStorage { Data = download.DownloadBinary }; - } - else - { - // move to file system. it's necessary because from now on DownloadService depends on current storage provider - // and it would not find the binary data anymore if not moved. - var fileName = GetFileName(download.Id, download.Extension, download.ContentType); - var path = fileSystem.Combine(fileSystem.Combine(mediaBasePath, "Downloads"), fileName); - - fileSystem.WriteAllBytes(path, download.DownloadBinary); + if (storeMediaInDb) + { + // move binary data + download.MediaStorage = new MediaStorage { Data = download.DownloadBinary }; + } + else + { + // move to file system. it's necessary because from now on DownloadService depends on current storage provider + // and it would not find the binary data anymore if not moved. + var fileName = GetFileName(download.Id, download.Extension, download.ContentType); + var path = fileSystem.Combine(fileSystem.Combine(mediaBasePath, "Downloads"), fileName); + + fileSystem.WriteAllBytes(path, download.DownloadBinary); + } + + download.DownloadBinary = null; } + }); - download.DownloadBinary = null; - } - }); - - #endregion + #endregion - #region Queued email attachments + #region Queued email attachments - var attachmentQuery = context.Set() - .Where(x => x.StorageLocation == EmailAttachmentStorageLocation.Blob) - .OrderBy(x => x.Id); + var attachmentQuery = context.Set() + .Where(x => x.StorageLocation == EmailAttachmentStorageLocation.Blob) + .OrderBy(x => x.Id); - PageEntities(context, mediaStorages, attachmentQuery, attachment => - { - if (attachment.Data != null && attachment.Data.LongLength > 0) + PageEntities(context, mediaStorages, attachmentQuery, attachment => { - if (storeMediaInDb) + if (attachment.Data != null && attachment.Data.LongLength > 0) { - // move binary data - attachment.MediaStorage = new MediaStorage { Data = attachment.Data }; + if (storeMediaInDb) + { + // move binary data + attachment.MediaStorage = new MediaStorage { Data = attachment.Data }; + } + else + { + // move to file system. it's necessary because from now on QueuedEmailService depends on current storage provider + // and it would not find the binary data anymore if do not move it. + var fileName = GetFileName(attachment.Id, Path.GetExtension(attachment.Name.EmptyNull()), attachment.MimeType); + var path = fileSystem.Combine(fileSystem.Combine(mediaBasePath, "QueuedEmailAttachment"), fileName); + + fileSystem.WriteAllBytes(path, attachment.Data); + } + + attachment.Data = null; } - else - { - // move to file system. it's necessary because from now on QueuedEmailService depends on current storage provider - // and it would not find the binary data anymore if do not move it. - var fileName = GetFileName(attachment.Id, Path.GetExtension(attachment.Name.EmptyNull()), attachment.MimeType); - var path = fileSystem.Combine(fileSystem.Combine(mediaBasePath, "QueuedEmailAttachment"), fileName); - - fileSystem.WriteAllBytes(path, attachment.Data); - } - - attachment.Data = null; - } - }); + }); - #endregion + #endregion + } #pragma warning restore 612, 618 } @@ -221,8 +224,9 @@ private void PageEntities( } GC.Collect(); + GC.WaitForPendingFinalizers(); - // load max 1000 entities at once + // load max 200 entities at once entities = new PagedList(query, pageIndex++, PAGE_SIZE); entities.Each(x => moveEntity(x)); diff --git a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs index a1ddb8c90a..5eda541bc0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201609201852449_log4net.cs @@ -11,13 +11,22 @@ 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;"); + // SQL CE: DropIndex fails during installation, IF EXISTS not supported. + //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 Menueintrgen 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 Menueintrgen 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 lschen"); + "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 ausfhren."); + "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 ausfhren. 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 gelscht."); + "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 fr {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 Mindestlnge fr den Suchbegriff betrgt {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 fr {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 -Vorschlge werden schon whrend 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 Suchbegrifflnge", + "Minimale Suchbegrifflänge", "Specifies the minimum length of a search term from which to show the result of instant search.", - "Legt die minimale Lnge 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 zustzlich zu durchsuchende Felder fest. Der Produktname wird grundstzlich 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 grer 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 ungltig. Bitte geben Sie einen gltigen Pfad ein."); + "Der Pfad \"{0}\" ist ungültig. Bitte geben Sie einen gültigen Pfad ein."); builder.AddOrUpdate("Common.AdditionalShippingSurcharge", - "zzgl. {0} zustzlicher Versandgebhr", + "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 grundstzlich 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", - "Auswahlmglichkeiten fr Seitengre", + "Auswahlmöglichkeiten für Seitengröße", "Comma-separated page size options that a customer can select in product lists.", - "Kommagetrennte Liste mit Optionen fr Seitengre, die ein Kunde in Produktlisten whlen 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", "Regulr"); + builder.AddOrUpdate("Products.Price.OldPrice", "Regular", "Regulär"); builder.AddOrUpdate("Products.Sku", "SKU", "Art.-Nr."); - builder.AddOrUpdate("Products.ChooseColorX", "Choose {0}", "{0} auswhlen"); + 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 groen 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 knnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. Fr 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 (abhngig 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 gelscht 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 Herstellerbersichtsseite 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", "Vorrtig"); + 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 fr 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: Hchste 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 fr 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 fr 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 erhht bzw. vermindert wird, wenn ein Kunde die +/- Steuerelemente benutzt. Die Bestellmenge ist auf ein Vielfaches dieses Wertes beschrnkt."); + "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 fr 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 hinzugefgt"); + 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 schtzen"); + builder.AddOrUpdate("ShoppingCart.EstimateShipping", "Estimate shipping", "Versandkosten schätzen"); - builder.AddOrUpdate("PageTitle.Blog.Month", "Blog entries in {0}", "Blog Eintrge des Monats {0}"); - builder.AddOrUpdate("PageTitle.Blog.Tag", "Blog entries for the tag {0}", "Blog-Eintrge fr das Stichwort {0}"); - builder.AddOrUpdate("Metadesc.Blog.Month", "Blog entries in {0}", "Blog Eintrge des Monats {0}"); - builder.AddOrUpdate("Metadesc.Blog.Tag", "Blog entries for the tag {0}", "Blog-Eintrge fr 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.", - "Whlen Sie ein Bild, welches als Auswahlelement fr 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 whlen 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 hinzufgen"); + 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}\"?", - "Mchten 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 zusammenfhren"); + "Alle Optionen zusammenführen"); builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.DeleteExistingValues", "Delete existing options", - "Vorhandene Optionen lschen"); + "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 fr 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", "Verfgbarkeit"); + 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} ausgewhlt"); + 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 verfgbare Artikel einschlieen"); + 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", - "Kontrollkstchen"); + "Kontrollkästchen"); builder.AddOrUpdate("Enums.SmartStore.Core.Search.Facets.FacetTemplateHint.Custom", "Boxes (color & image)", - "Kstchen (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 nchsten 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 ermglichen", + "Filtern ermöglichen", "Specifies whether search results can be filtered by this attribute. " + megaSearchPlusHintEn, - "Legt fest, ob Suchergebnisse nach diesem Attribut gefiltert werden knnen. " + 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 knnen. " + 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 knnen. " + 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 ermglichen (z.B. hellrot bis dunkelrot). Fr 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 zurcksetzen"); + 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-Schlsselwort", + "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-Schlsselwort bestimmt den Platz im Shop, an welchem die Umfrage dargestellt wird. Verfgbare System-Schlsselwrter 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", "Rcksendegrund"); - builder.AddOrUpdate("Account.CustomerReturnRequests.Action", "Return action", "Rcksendeaktion"); + 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 fgen Sie diesen hier ein. - Auf diese Weise knnen 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 Teilrckerstattung ist fr diese Bestellung nicht mglich."); + .Value("de", "Eine Teilrückerstattung ist für diese Bestellung nicht möglich."); builder.AddOrUpdate("Order.CannotRefund") - .Value("de", "Eine Rckerstattung ist fr diese Bestellung nicht mglich."); + .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 mglich."); + .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 Schaltflche '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", "Ursprngliche 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. Fr 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 gewhlten 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 fhren wrde. 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)", - "Kstchen (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 Kufer kann die Zahlung durchfhren, 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 whlen Sie die Schriftarten, die Sie in Ihrem Shop verwenden mchten. + Gehen Sie zu https://fonts.google.com/ und wählen Sie die Schriftarten, die Sie in Ihrem Shop verwenden möchten.
  • - Als Html-Code fr 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"">
  • - Fgen Sie den fett markierten Teil des Links in eins der drei Felder des Theme-Konfigurators ein, die fr 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 knnen Sie die Schriftart verwenden, indem Sie den Namen der Schriftart (z.B. Roboto) in den Eingabefeldern angeben, - die fr 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 verfgabren 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 fr 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 fr 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}", "Fr: {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} zustzlicher Versandgebhr"); + "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 Eintrge fr 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 Verfgung 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 Verfgung 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, verknpfte 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 bentigt und kann daher nicht gelscht 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", + "Auffllung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.WalletPostingReason.Refund", + "Refund", + "Rckerstattung"); + 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-Eintrgen der obersten Hierarchie fest, die im Katalogmen angezeigt werden. Alle weiteren Menu-Eintrge 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 verfgbaren 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 verfgbaren Sprachen fr Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verfgbaren Ressourcen."); + + builder.AddOrUpdate("Admin.Configuration.Languages.InstalledLanguages", + "Installed Languages", + "Installierte Sprachen"); + builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages", + "Available Languages", + "Verfgbare 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 verfgbaren 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 verffentlichte Sprache ist erforderlich."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.AvailableLanguageSetId", + "Available Languages", + "Verfgbare Sprachen", + "Specifies the available language whose localized resources are to be imported.", + "Legt die verfgbare 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 whlen Sie eine verfgbare Sprache, deren Ressourcen importiert werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.ChargeOnlyHighestProductShippingSurcharge", + "Charge the highest shipping surcharge only", + "Nur den hchsten Transportzuschlag berechnen", + "Specifies whether to charge only the highest additional shipping surcharge of products.", + "Bestimmt ob bei der Berechnung der Versandkosten nur der hchste Transportzuschlag von Produkten bercksichtigt 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 fr 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 fr 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 knnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. Fr 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 gehrenden 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 fhren 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 mchten."); + + builder.Delete("Account.PasswordRecoveryConfirm.InvalidEmail"); + builder.Delete("Account.PasswordRecoveryConfirm.InvalidToken"); + + builder.AddOrUpdate("Admin.Common.Acl.SubjectTo", + "Restrict access", + "Zugriff einschrnken", + "Determines whether this entity is subject to access restrictions (no = no restriction, yes = accessible only for selected customer groups)", + "Legt fest, ob dieser Datensatz Zugriffsbeschrnkungen unterliegt (Nein = keine Beschrnkung, Ja = zugnglich nur fr gewhlte 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.", + "Whlen Sie Kundengruppen, die auf den Datensatz zugreifen knnen. 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 gewhlte Zahlart muss hierfr Buchungen untersttzen."); + + #region taken from V22Final, because they were never added yet + + builder.AddOrUpdate("Common.Next", + "Next", + "Weiter"); + builder.AddOrUpdate("Admin.Common.BackToConfiguration", + "Back to configuration", + "Zurck 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", + "Ausgewhlte 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 zugefgt"); + + 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 <Formatprfix>:<Export-Feldname> fest. Beispiel: gmc:color exportiert beim Google Merchant Center Export die Attributwerte fr 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.", + "Ermglicht die Zuordnung von Attributwerten zu Exportfeldern. Jeder Eintrag muss in einer neuen Zeile erfolgen."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.AdditionalFee", + "Additional fee", + "Zustzliche Gebhr", + "Specifies an additional fee to be charged to the customer for using the payment method.", + "Legt eine zustzliche Gebhr fest, die dem Kunden fr die Inanspruchnahme der Zahlart berechnet wird."); + + builder.AddOrUpdate("Admin.Configuration.Payment.Methods.AdditionalFeePercentage", + "Additional fee percentage", + "Zustzliche Gebhr 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 zustzliche Gebhr 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 bentigt"); + + 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 bentigt und kann daher nicht gelscht 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 knnen und ob die Checkbox dafr standardmig 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 fr 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 ermglichen. Zum Teil werden Cookies auch von Drittanbietern gesetzt. Datenschutzerklrung"); + + 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", + "Einwilligungserklrung 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 Datenschutzerklrung 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", "Gelscht"); + builder.AddOrUpdate("Gdpr.DeletedLongText", + "This content was deleted by the author.", + "Dieser Inhalt wurde vom Autor gelscht."); + 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}Datenschutzerklrung{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 mchte den Newsletter abonnieren. Mit den Bestimmungen zum Datenschutz bin ich einverstanden. Eine Abmeldung ist jederzeit mglich."); + + 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 zuverlssige Bot-Erkennungsmethode, die ganz ohne Captcha auskommt. Wenn aktiv, werden Registrierungs- und Kontaktformular vor Bots und Angreifern geschtzt."); + } + } +} 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..f2d93452c9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201806231547270_ScheduleTaskHistory.cs @@ -0,0 +1,93 @@ +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", "Ausgefhrt 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.HistoryCleanupNote", + "The history is cleaned up once a day: maximum {0} entries and none older than {1} days.", + "Die Historie wird einmal tglich bereinigt: Maximal {0} Eintrge 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/201809261026134_ForumGroupAcl.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.Designer.cs new file mode 100644 index 0000000000..55b7fcd313 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.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 ForumGroupAcl : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ForumGroupAcl)); + + string IMigrationMetadata.Id + { + get { return "201809261026134_ForumGroupAcl"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs new file mode 100644 index 0000000000..4abd203c4a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.cs @@ -0,0 +1,48 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ForumGroupAcl : DbMigration + { + public override void Up() + { + DropIndex("dbo.Forums_Topic", new[] { "ForumId" }); + DropIndex("dbo.Forums_Forum", new[] { "ForumGroupId" }); + AddColumn("dbo.Forums_Post", "Published", c => c.Boolean(nullable: false, defaultValue: true)); + AddColumn("dbo.Forums_Topic", "Published", c => c.Boolean(nullable: false, defaultValue: true)); + AddColumn("dbo.Forums_Group", "SubjectToAcl", c => c.Boolean(nullable: false)); + CreateIndex("dbo.Forums_Post", "CreatedOnUtc"); + CreateIndex("dbo.Forums_Post", "Published"); + CreateIndex("dbo.Forums_Topic", new[] { "ForumId", "Published" }); + CreateIndex("dbo.Forums_Topic", new[] { "TopicTypeId", "LastPostTime" }); + CreateIndex("dbo.Forums_Topic", "Subject"); + CreateIndex("dbo.Forums_Topic", "NumPosts"); + CreateIndex("dbo.Forums_Topic", "CreatedOnUtc"); + CreateIndex("dbo.Forums_Forum", new[] { "ForumGroupId", "DisplayOrder" }); + CreateIndex("dbo.Forums_Group", "DisplayOrder"); + CreateIndex("dbo.Forums_Group", "LimitedToStores"); + CreateIndex("dbo.Forums_Group", "SubjectToAcl"); + } + + public override void Down() + { + DropIndex("dbo.Forums_Group", new[] { "SubjectToAcl" }); + DropIndex("dbo.Forums_Group", new[] { "LimitedToStores" }); + DropIndex("dbo.Forums_Group", new[] { "DisplayOrder" }); + DropIndex("dbo.Forums_Forum", new[] { "ForumGroupId", "DisplayOrder" }); + DropIndex("dbo.Forums_Topic", new[] { "CreatedOnUtc" }); + DropIndex("dbo.Forums_Topic", new[] { "NumPosts" }); + DropIndex("dbo.Forums_Topic", new[] { "Subject" }); + DropIndex("dbo.Forums_Topic", new[] { "TopicTypeId", "LastPostTime" }); + DropIndex("dbo.Forums_Topic", new[] { "ForumId", "Published" }); + DropIndex("dbo.Forums_Post", new[] { "Published" }); + DropIndex("dbo.Forums_Post", new[] { "CreatedOnUtc" }); + DropColumn("dbo.Forums_Group", "SubjectToAcl"); + DropColumn("dbo.Forums_Topic", "Published"); + DropColumn("dbo.Forums_Post", "Published"); + CreateIndex("dbo.Forums_Forum", "ForumGroupId"); + CreateIndex("dbo.Forums_Topic", "ForumId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.resx b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.resx new file mode 100644 index 0000000000..07a094ec2a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201809261026134_ForumGroupAcl.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/201810011954195_ForumPostVote.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.Designer.cs new file mode 100644 index 0000000000..df1f55332e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.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 ForumPostVote : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ForumPostVote)); + + string IMigrationMetadata.Id + { + get { return "201810011954195_ForumPostVote"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs new file mode 100644 index 0000000000..843ad1b291 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.cs @@ -0,0 +1,35 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class ForumPostVote : DbMigration + { + public override void Up() + { + CreateTable( + "dbo.ForumPostVote", + c => new + { + Id = c.Int(nullable: false), + ForumPostId = c.Int(nullable: false), + Vote = c.Boolean(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.CustomerContent", t => t.Id) + .ForeignKey("dbo.Forums_Post", t => t.ForumPostId, cascadeDelete: true) + .Index(t => t.Id) + .Index(t => t.ForumPostId); + + } + + public override void Down() + { + DropForeignKey("dbo.ForumPostVote", "ForumPostId", "dbo.Forums_Post"); + DropForeignKey("dbo.ForumPostVote", "Id", "dbo.CustomerContent"); + DropIndex("dbo.ForumPostVote", new[] { "ForumPostId" }); + DropIndex("dbo.ForumPostVote", new[] { "Id" }); + DropTable("dbo.ForumPostVote"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.resx new file mode 100644 index 0000000000..1363d329af --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810011954195_ForumPostVote.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/201810231214068_DataExchangeEnhancements.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.Designer.cs new file mode 100644 index 0000000000..9cf3ad8ecd --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.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 DataExchangeEnhancements : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(DataExchangeEnhancements)); + + string IMigrationMetadata.Id + { + get { return "201810231214068_DataExchangeEnhancements"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs new file mode 100644 index 0000000000..1bdcda90d3 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.cs @@ -0,0 +1,50 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class DataExchangeEnhancements : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + DropIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + AddColumn("dbo.ImportProfile", "ImportRelatedData", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportProfile", "ExportRelatedData", c => c.Boolean(nullable: false)); + AlterColumn("dbo.ScheduleTaskHistory", "MachineName", c => c.String(nullable: false, maxLength: 400)); + CreateIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + } + + public override void Down() + { + DropIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + AlterColumn("dbo.ScheduleTaskHistory", "MachineName", c => c.String(maxLength: 400)); + DropColumn("dbo.ExportProfile", "ExportRelatedData"); + DropColumn("dbo.ImportProfile", "ImportRelatedData"); + CreateIndex("dbo.ScheduleTaskHistory", new[] { "MachineName", "IsRunning" }); + } + + public bool RollbackOnFailure => false; + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.DataExchange.Export.ExportRelatedData", + "Export associated data", + "Zugehrige Daten exportieren", + "Specifies whether to also export associated data (e.g. tier prices).", + "Legt fest, ob auch zugehrige Daten (z.B. Staffelpreise) exportiert werden sollen."); + + builder.AddOrUpdate("Admin.DataExchange.Import.ImportRelatedData", + "Import associated data", + "Zugehrige Daten importieren", + "Specifies whether to also import associated data (e.g. tier prices).", + "Legt fest, ob auch zugehrige Daten (z.B. Staffelpreise) importiert werden sollen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.resx new file mode 100644 index 0000000000..7fd395473e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201810231214068_DataExchangeEnhancements.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/201811061745204_IsSystemProductIndex.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.Designer.cs new file mode 100644 index 0000000000..65a34605b6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.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 IsSystemProductIndex : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(IsSystemProductIndex)); + + string IMigrationMetadata.Id + { + get { return "201811061745204_IsSystemProductIndex"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs new file mode 100644 index 0000000000..a51d10dc8f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.cs @@ -0,0 +1,29 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Core.Data; + + public partial class IsSystemProductIndex : DbMigration + { + public override void Up() + { + DropIndex("dbo.Product", "IX_Product_Deleted_and_Published"); + if (DataSettings.Current.IsSqlServer) + { + RenameIndex(table: "dbo.Product", name: "Product_SystemName_IsSystemProduct", newName: "IX_Product_SystemName_IsSystemProduct"); + } + CreateIndex("dbo.Product", new[] { "Published", "Deleted", "IsSystemProduct" }, name: "IX_Product_Published_Deleted_IsSystemProduct"); + } + + public override void Down() + { + DropIndex("dbo.Product", "IX_Product_Published_Deleted_IsSystemProduct"); + if (DataSettings.Current.IsSqlServer) + { + RenameIndex(table: "dbo.Product", name: "IX_Product_SystemName_IsSystemProduct", newName: "Product_SystemName_IsSystemProduct"); + } + CreateIndex("dbo.Product", new[] { "Published", "Deleted" }, name: "IX_Product_Deleted_and_Published"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.resx b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.resx new file mode 100644 index 0000000000..fb87b5d8c0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811061745204_IsSystemProductIndex.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/201811082148279_LocalizedPropertyKeyGroupIndex.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.Designer.cs new file mode 100644 index 0000000000..d342d27f89 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.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 LocalizedPropertyKeyGroupIndex : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(LocalizedPropertyKeyGroupIndex)); + + string IMigrationMetadata.Id + { + get { return "201811082148279_LocalizedPropertyKeyGroupIndex"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.cs new file mode 100644 index 0000000000..b5aef74649 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class LocalizedPropertyKeyGroupIndex : DbMigration + { + public override void Up() + { + CreateIndex("dbo.LocalizedProperty", "LocaleKeyGroup", name: "IX_LocalizedProperty_LocaleKeyGroup"); + } + + public override void Down() + { + DropIndex("dbo.LocalizedProperty", "IX_LocalizedProperty_LocaleKeyGroup"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.resx b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.resx new file mode 100644 index 0000000000..2b45068970 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811082148279_LocalizedPropertyKeyGroupIndex.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/201811161142587_TaskHistoryErrorLength.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.Designer.cs new file mode 100644 index 0000000000..61f95300e6 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.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 TaskHistoryErrorLength : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TaskHistoryErrorLength)); + + string IMigrationMetadata.Id + { + get { return "201811161142587_TaskHistoryErrorLength"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.cs b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.cs new file mode 100644 index 0000000000..6a1180c68d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class TaskHistoryErrorLength : DbMigration + { + public override void Up() + { + AlterColumn("dbo.ScheduleTaskHistory", "Error", c => c.String()); + } + + public override void Down() + { + AlterColumn("dbo.ScheduleTaskHistory", "Error", c => c.String(maxLength: 1000)); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.resx b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.resx new file mode 100644 index 0000000000..95332172d1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811161142587_TaskHistoryErrorLength.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/201811202204501_ProductIndexSeekExport.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.Designer.cs new file mode 100644 index 0000000000..4205b2c00f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.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 ProductIndexSeekExport : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ProductIndexSeekExport)); + + string IMigrationMetadata.Id + { + get { return "201811202204501_ProductIndexSeekExport"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.cs b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.cs new file mode 100644 index 0000000000..e78d62dc3f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.cs @@ -0,0 +1,35 @@ +namespace SmartStore.Data.Migrations +{ + using SmartStore.Core.Data; + using System; + using System.Data.Entity.Migrations; + + public partial class ProductIndexSeekExport : DbMigration + { + public override void Up() + { + if (DataSettings.Current.IsSqlServer) + { + Sql(@"CREATE NONCLUSTERED INDEX [IX_SeekExport1] ON [dbo].[Product] +( + [Published] ASC, + [Id] ASC, + [VisibleIndividually] ASC, + [Deleted] ASC, + [IsSystemProduct] ASC, + [AvailableStartDateTimeUtc] ASC, + [AvailableEndDateTimeUtc] ASC +) +INCLUDE ([UpdatedOnUtc]) WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]"); + } + } + + public override void Down() + { + if (DataSettings.Current.IsSqlServer) + { + DropIndex("dbo.Product", "IX_SeekExport1"); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.resx b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.resx new file mode 100644 index 0000000000..95332172d1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201811202204501_ProductIndexSeekExport.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/201902211855242_TopicHtmlIdAndBodyCss.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.Designer.cs new file mode 100644 index 0000000000..4d82aade02 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.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 TopicHtmlIdAndBodyCss : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(TopicHtmlIdAndBodyCss)); + + string IMigrationMetadata.Id + { + get { return "201902211855242_TopicHtmlIdAndBodyCss"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.cs b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.cs new file mode 100644 index 0000000000..83f8e64ce5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.cs @@ -0,0 +1,20 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class TopicHtmlIdAndBodyCss : DbMigration + { + public override void Up() + { + AddColumn("dbo.Topic", "HtmlId", c => c.String(maxLength: 128)); + AddColumn("dbo.Topic", "BodyCssClass", c => c.String(maxLength: 512)); + } + + public override void Down() + { + DropColumn("dbo.Topic", "BodyCssClass"); + DropColumn("dbo.Topic", "HtmlId"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.resx b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.resx new file mode 100644 index 0000000000..2b78eeae9f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201902211855242_TopicHtmlIdAndBodyCss.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/201904110735029_Menus.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.Designer.cs new file mode 100644 index 0000000000..b943551be8 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.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 Menus : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Menus)); + + string IMigrationMetadata.Id + { + get { return "201904110735029_Menus"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.cs b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.cs new file mode 100644 index 0000000000..645fdd9314 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.cs @@ -0,0 +1,90 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Core.Data; + using SmartStore.Data.Setup; + using SmartStore.Data.Utilities; + + public partial class Menus : DbMigration, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.MenuRecord", + c => new + { + Id = c.Int(nullable: false, identity: true), + SystemName = c.String(nullable: false, maxLength: 400), + IsSystemMenu = c.Boolean(nullable: false), + Template = c.String(maxLength: 400), + WidgetZone = c.String(maxLength: 4000), + Title = c.String(maxLength: 400), + Published = c.Boolean(nullable: false), + DisplayOrder = c.Int(nullable: false), + LimitedToStores = c.Boolean(nullable: false), + SubjectToAcl = c.Boolean(nullable: false), + }) + .PrimaryKey(t => t.Id) + .Index(t => new { t.SystemName, t.IsSystemMenu }, name: "IX_Menu_SystemName_IsSystemMenu") + .Index(t => t.Published, name: "IX_Menu_Published") + .Index(t => t.LimitedToStores, name: "IX_Menu_LimitedToStores") + .Index(t => t.SubjectToAcl, name: "IX_Menu_SubjectToAcl"); + + CreateTable( + "dbo.MenuItemRecord", + c => new + { + Id = c.Int(nullable: false, identity: true), + MenuId = c.Int(nullable: false), + ParentItemId = c.Int(nullable: false), + ProviderName = c.String(maxLength: 100), + Model = c.String(), + Title = c.String(maxLength: 400), + ShortDescription = c.String(maxLength: 400), + PermissionNames = c.String(), + Published = c.Boolean(nullable: false), + DisplayOrder = c.Int(nullable: false), + BeginGroup = c.Boolean(nullable: false), + ShowExpanded = c.Boolean(nullable: false), + NoFollow = c.Boolean(nullable: false), + NewWindow = c.Boolean(nullable: false), + Icon = c.String(maxLength: 100), + Style = c.String(maxLength: 10), + HtmlId = c.String(maxLength: 100), + CssClass = c.String(maxLength: 100), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.MenuRecord", t => t.MenuId, cascadeDelete: true) + .Index(t => t.MenuId) + .Index(t => t.ParentItemId, name: "IX_MenuItem_ParentItemId") + .Index(t => t.Published, name: "IX_MenuItem_Published") + .Index(t => t.DisplayOrder, name: "IX_MenuItem_DisplayOrder"); + + } + + public override void Down() + { + DropForeignKey("dbo.MenuItemRecord", "MenuId", "dbo.MenuRecord"); + DropIndex("dbo.MenuItemRecord", "IX_MenuItem_DisplayOrder"); + DropIndex("dbo.MenuItemRecord", "IX_MenuItem_Published"); + DropIndex("dbo.MenuItemRecord", "IX_MenuItem_ParentItemId"); + DropIndex("dbo.MenuItemRecord", new[] { "MenuId" }); + DropIndex("dbo.MenuRecord", "IX_Menu_SubjectToAcl"); + DropIndex("dbo.MenuRecord", "IX_Menu_LimitedToStores"); + DropIndex("dbo.MenuRecord", "IX_Menu_Published"); + DropIndex("dbo.MenuRecord", "IX_Menu_SystemName_IsSystemMenu"); + DropTable("dbo.MenuItemRecord"); + DropTable("dbo.MenuRecord"); + } + + public bool RollbackOnFailure => true; + + public void Seed(SmartObjectContext context) + { + if (DataSettings.DatabaseIsInstalled()) + { + DataMigrator.CreateSystemMenus(context); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.resx b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.resx new file mode 100644 index 0000000000..3c944a5f47 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201904110735029_Menus.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/201905020948354_WidgetTopics.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.Designer.cs new file mode 100644 index 0000000000..7a420fcffe --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.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 WidgetTopics : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(WidgetTopics)); + + string IMigrationMetadata.Id + { + get { return "201905020948354_WidgetTopics"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.cs b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.cs new file mode 100644 index 0000000000..304479485c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.cs @@ -0,0 +1,49 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using System.Linq; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Topics; + using SmartStore.Data.Setup; + + public partial class WidgetTopics : DbMigration, IDataSeeder + { + public override void Up() + { + } + + public override void Down() + { + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + using (var scope = new DbContextScope(ctx: context, validateOnSave: false, hooksEnabled: false)) + { + var widgetTopics = new[] + { + "CheckoutAsGuestOrRegister", + "ContactUs", + "ForumWelcomeMessage", + "HomePageText", + "LoginRegistrationInfo" + }; + + var topics = context.Set().Where(x => widgetTopics.Contains(x.SystemName)).ToList(); + topics.Each(x => + { + x.RenderAsWidget = true; + x.WidgetWrapContent = false; + }); + + context.SaveChanges(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.resx b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.resx new file mode 100644 index 0000000000..274628a4fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905020948354_WidgetTopics.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/201905101159134_V320Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.Designer.cs new file mode 100644 index 0000000000..e07144c09d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.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 V320Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V320Resources)); + + string IMigrationMetadata.Id + { + get { return "201905101159134_V320Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.cs new file mode 100644 index 0000000000..231a748e84 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.cs @@ -0,0 +1,876 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Catalog; + using SmartStore.Data.Setup; + using SmartStore.Utilities; + using SmartStore.Core.Domain.Common; + + public partial class V320Resources : 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) + { + context.MigrateSettings(x => + { + x.Add(TypeHelper.NameOf(y => y.CacheSegmentSize, true), 500); + x.Add(TypeHelper.NameOf(y => y.AlwaysPrefetchTranslations, true), false); + x.Add(TypeHelper.NameOf(y => y.AlwaysPrefetchUrlSlugs, true), false); + + // New CatalogSettings properties + x.Add(TypeHelper.NameOf(y => y.ShowSubCategoriesInSubPages, true), false); + x.Add(TypeHelper.NameOf(y => y.ShowDescriptionInSubPages, true), false); + x.Add(TypeHelper.NameOf(y => y.IncludeFeaturedProductsInSubPages, true), false); + }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + 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 fr diesen Rcksendewunsch 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 gltigen, relativen Ordnerpfad fr 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 grer 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 Bercksichtigung 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 ungltig."); + + // Get rid of duplicate validator resource entries + builder.Delete( + "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-Prioritt fest. 1 steht bspw. fr 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 Kontrollkstchen klicken, sondern die Validierung erfolgt direkt beim Absenden eines Formulars. Nur bei 'verdchtigem' Traffic wird der Benutzer aufgefordert, ein Captcha zu lsen."); + + 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 lschen"); + 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 lschen."); + builder.AddOrUpdate("Admin.Catalog.Products.Download.AddChangelog", "Edit changelog", "nderungshistorie bearbeiten"); + builder.AddOrUpdate("Customer.Downloads.NoChangelogAvailable", "No changelog available.", "Keine nderungshistorie verfgbar."); + + 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 gltig. 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 ausschlieen", + "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 Schlsselwrtern, die das Produkt taxonomisch charakterisieren. Je mehr Produkte einem Schlsselwort (Tag) zugeordnet sind, desto mehr visuelles Gewicht erhlt 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 Beitrge: aufsteigend"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Forums.ForumTopicSorting.PostsDesc", "Post number: descending", "Anzahl Beitrge: 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 fr {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 groen 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 (abhngig 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 zustzlich zu durchsuchende Felder fest. Der Thementitel wird grundstzlich 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 Forenbeitrge sortiert werden knnen."); + + builder.AddOrUpdate("Admin.Common.DefaultPageSizeOptions", + "Page size options", + "Auswahlmglichkeiten fr Seitengre", + "Comma-separated page size options that a customer can select in lists.", + "Kommagetrennte Liste mit Optionen fr Seitengre, die ein Kunde in Listen whlen kann."); + + builder.AddOrUpdate("Admin.Common.AllowCustomersToSelectPageSize", + "Allow customers to select page size", + "Kunde kann Listengre ndern", + "Whether customers are allowed to select the page size from a predefined list of options.", + "Kunden knnen die Listengre mit Hilfe einer vorgegebenen Optionsliste ndern."); + + + builder.Delete( + "Admin.Configuration.Settings.Search.DefaultSortOrderMode", + "Admin.Configuration.Settings.Search.InstantSearchNumberOfProducts", + "Admin.Configuration.Settings.CustomerUser.DefaultAvatarEnabled", + "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."); + + builder.AddOrUpdate("Admin.Catalog.Products.BundleItems.NoProductLinkageForBundleItem", + "The product \"{0}\" cannot be assigned an attribute of the type \"product\" because it is bundle item of a product bundle.", + "Dem Produkt \"{0}\" kann kein Attribut vom Typ \"Produkt\" zugeordnet werden, weil es auf der Stckliste eines Produkt-Bundle steht."); + + builder.AddOrUpdate("Search.RelatedSearchTerms", + "Related search terms", + "Verwandte Suchbegriffe"); + + builder.AddOrUpdate("Plugins.CannotLoadModule", + "The plugin or provider \"{0}\" cannot be loaded.", + "Das Plugin oder der Provider \"{0}\" kann nicht geladen werden."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunPerMachine", + "Run per machine", + "Pro Maschine ausfhren", + "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 ausgefhrt wird."); + + builder.Delete("Address.Fields.Required.Hint"); + + builder.AddOrUpdate("Common.FormFields.Required.Hint", + "* Input elements with asterisk are required and have to be filled out.", + "* Eingabefelder mit Sternchen sind Pflichfelder und mssen ausgefllt werden."); + + builder.AddOrUpdate("Forum.Post.Vote.OnlyRegistered", + "Only registered users can vote for posts.", + "Nur registrierte Benutzer knnen Beitrge bewerten."); + + builder.AddOrUpdate("Forum.Post.Vote.OwnPostNotAllowed", + "You cannot vote for your own post.", + "Sie knnen nicht Ihren eigenen Beitrag bewerten."); + + builder.AddOrUpdate("Forum.Post.Vote.SuccessfullyVoted", + "Thank you for your vote.", + "Danke fr Ihre Bewertung."); + + builder.AddOrUpdate("Common.Liked", "Liked", "Gefllt"); + builder.AddOrUpdate("Common.LikeIt", "I like it", "Gefllt mir"); + builder.AddOrUpdate("Common.DoNotLikeIt", "I do not like it anymore", "Gefllt mir nicht mehr"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowCustomersToVoteOnPosts", + "Allow customers to vote on posts", + "Benutzer knnen Beitrge bewerten", + "Specifies whether customers can vote on posts.", + "Legt fest, ob Benutzer Beitrge bewerten knnen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Forums.AllowGuestsToVoteOnPosts", + "Allow guests to vote on posts", + "Gste knnen Beitrge bewerten", + "Specifies whether guests can vote on posts.", + "Legt fest, ob Gste Beitrge bewerten knnen."); + + // Typos. + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements") + .Value("de", "Voraussetzungen"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.DiscountRequirementType") + .Value("de", "Typ der Voraussetzung"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.DiscountRequirementType.Hint") + .Value("de", "Voraussetzungen fr den Rabatt"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.Remove") + .Value("de", "Voraussetzung fr den Rabatt entfernen"); + builder.AddOrUpdate("Admin.Promotions.Discounts.Requirements.SaveBeforeEdit") + .Value("de", "Sie mssen den Rabatt zunchst speichern, bevor Sie Voraussetzungen fr seine Anwendung festlegen knnen"); + + builder.AddOrUpdate("Common.Voting", "Voting", "Abstimmung"); + builder.AddOrUpdate("Common.Answer", "Answer", "Antwort"); + builder.AddOrUpdate("Common.Size", "Size", "Gre"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerFormFields.Description", + "Manage form fields that are displayed during registration.", + "Verwalten Sie Formularfelder, die whrend der Registrierung angezeigt werden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.AddressFormFields.Description", + "Manage form fields that are displayed during checkout and on \"My account\" page.", + "Verwalten Sie Formularfelder, die whrend des Checkout-Prozesses und im \"Mein Konto\" Bereich angezeigt werden."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.RelatedEntityType.TierPrice", "Tier price", "Staffelpreis"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.RelatedEntityType.ProductVariantAttributeValue", "Attribute option", "Attribut-Option"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.RelatedEntityType.ProductVariantAttributeCombination", "Attribute combination", "Attribut-Kombination"); + + builder.AddOrUpdate("Admin.DataExchange.Export.ExportRelatedData.Validate", + "Related data cannot be exported if the option \"Export attribute combinations\" is activated.", + "Zugehrige Daten knnen nicht exportiert werden, wenn die Option \"Attributkombinationen exportieren\" aktiviert ist."); + + builder.AddOrUpdate("Admin.Common.ProcessingInfo", + "{0}: {1} of {2} processed", + "{0}: {1} von {2} verarbeitet"); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowSubCategoriesInSubPages", + "Show subcategories also in subpages", + "Unterwarengruppen auch in Unterseiten anzeigen", + "Subpage: List index greater than 1 or any active filter.", + "Unterseite: Listenindex grer 1 oder mind. ein aktiver Filter."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowDescriptionInSubPages", + "Show page description also in subpages", + "Seitenbeschreibungen auch in Unterseiten anzeigen", + "Subpage: List index greater than 1 or any active filter.", + "Unterseite: Listenindex grer 1 oder mind. ein aktiver Filter."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.IncludeFeaturedProductsInSubPages", + "Show featured products also in subpages", + "Top-Produkte auch in Unterseiten anzeigen", + "Subpage: List index greater than 1 or any active filter.", + "Unterseite: Listenindex grer 1 oder mind. ein aktiver Filter."); + + builder.AddOrUpdate("Admin.Common.CopyOf", "Copy of {0}", "Kopie von {0}"); + + builder.AddOrUpdate("Admin.Configuration.Languages.DefaultLanguage.Note", + "The default language of the shop is {0}. The default is always the first published language.", + "Die Standardsprache des Shops ist {0}. Standard ist stets die erste verffentlichte Sprache."); + + 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 verfgbaren Ressourcen."); + + builder.AddOrUpdate("Common.BrowseFiles", "Browse", "Durchsuchen"); + builder.AddOrUpdate("Common.Url", "URL", "URL"); + builder.AddOrUpdate("Common.File", "File", "Datei"); + builder.AddOrUpdate("Common.Entity.Product", "Product", "Produkt"); + builder.AddOrUpdate("Common.Entity.Category", "Category", "Warengruppe"); + builder.AddOrUpdate("Common.Entity.Manufacturer", "Manufacturer", "Hersteller"); + builder.AddOrUpdate("Common.Entity.Topic", "Topic", "Seite"); + + builder.AddOrUpdate("Common.Entity.SelectProduct", "Select product", "Produkt auswhlen"); + builder.AddOrUpdate("Common.Entity.SelectCategory", "Select category", "Warengruppe auswhlen"); + builder.AddOrUpdate("Common.Entity.SelectManufacturer", "Select manufacturer", "Hersteller auswhlen"); + builder.AddOrUpdate("Common.Entity.SelectTopic", "Select topic", "Seite auswhlen"); + + builder.Delete("Admin.Customers.Customers.List.SearchDeletedOnly"); + builder.AddOrUpdate("Admin.Customers.Customers.List.SearchActiveOnly", "Only activated customers", "Nur aktivierte Kunden"); + + builder.AddOrUpdate("Products.LoginForPrice", + "Prices will be displayed after login.", + "Preise werden nach Anmeldung angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowLoginForPriceNote", + "Show login for price note", + "Hinweis \"Preis nach Anmeldung\" anzeigen", + "Specifies whether to display a message stating that prices will not be displayed until login.", + "Legt fest, ob ein Hinweis erscheinen soll, dass Preise erst nach Anmeldung angezeigt werden."); + + builder.AddOrUpdate("Products.EmailAFriend.LoginNote", + "Please log in to use this function. Login now", + "Bitte melden Sie sich an, um diese Funktion nutzen zu knnen. Jetzt anmelden"); + + builder.AddOrUpdate("Account.Login.Fields.UsernameOrEmail", + "Username or email", + "Benutzername oder Email"); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerLoginType", + "Customer login type", + "Art des Kundenlogins", + "Specifies the customer login type.", + "Legt die Art des Kundenlogins fest."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerLoginType.Username", "Username", "Benutzername"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerLoginType.Email", "Email", "Email"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerLoginType.UsernameOrEmail", "Username or email", "Benutzername oder Email"); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.Fields.HtmlId", + "Html Id", + "Html-ID", + "Specifies the Html Id of the page.", + "Legt die Html-ID der Seite fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.Fields.BodyCssClass", + "Body CSS class", + "Body CSS-Klasse", + "Specifies the CSS class of the body element.", + "Legt die CSS-Klasse des Body-Elements fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Topics.Validation.NoWhiteSpace", + "Whitespace isn't allowed.", + "Leerzeichen sind nicht erlaubt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Tax.VatRequired", + "Customers must enter a VAT number", + "Kunden mssen eine Steuernummer angeben", + "Specifies whether customers must enter a VAT identification number.", + "Legt fest, ob Kunden bei der Registrierung eine Steuernummer angeben mssen."); + + builder.AddOrUpdate("Common.Top", "Top", "Oben"); + builder.AddOrUpdate("Common.Bottom", "Bottom", "Unten"); + builder.AddOrUpdate("Common.Left", "Left", "Links"); + builder.AddOrUpdate("Common.Right", "Right", "Rechts"); + builder.AddOrUpdate("Common.TopLeft", "Top left", "Links oben"); + builder.AddOrUpdate("Common.TopRight", "Top right", "Rechts oben"); + builder.AddOrUpdate("Common.BottomLeft", "Bottom left", "Links unten"); + builder.AddOrUpdate("Common.BottomRight", "Bottom right", "Rechts unten"); + builder.AddOrUpdate("Common.Center", "Center", "Mitte"); + builder.AddOrUpdate("Common.NoTitle", "No title", "Ohne Titel"); + builder.AddOrUpdate("Common.MoveUp", "Move up", "Nach oben"); + builder.AddOrUpdate("Common.MoveDown", "Move down", "Nach unten"); + + builder.AddOrUpdate("Common.IncreaseValue", "Increase value", "Wert erhhen"); + builder.AddOrUpdate("Common.DecreaseValue", "Decrease value", "Wert verringern"); + builder.AddOrUpdate("Common.QueryString", "Query string", "Query String"); + + builder.AddOrUpdate("Admin.ContentManagement.Menus", "Menus", "Mens"); + builder.AddOrUpdate("Admin.ContentManagement.AddMenu", "Add menu", "Men hinzufgen"); + builder.AddOrUpdate("Admin.ContentManagement.EditMenu", "Edit menu", "Men bearbeiten"); + + builder.AddOrUpdate("Admin.ContentManagement.MenuLinks", "Menu items", "Men Links"); + builder.AddOrUpdate("Admin.ContentManagement.AddMenuItem", "Add menu item", "Men Link hinzufgen"); + builder.AddOrUpdate("Admin.ContentManagement.EditMenuItem", "Edit menu item", "Men Link bearbeiten"); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.NoMenuItemsAvailable", + "There are no menu links available.", + "Es sind keine Men Links vorhanden."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.CannotBeDeleted", + "This menu is required by your shop and can therefore not be deleted.", + "Dieses Men wird von Ihrem Shop bentigt und kann daher nicht gelscht werden."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.CatalogNote", + "The category tree is dynamically integrated into the menu.", + "Der Warengruppenbaum wird dynamisch in das Men eingebunden."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.SpecifyLinkTarget", + "Please specify link target", + "Bitte Link Ziel angeben"); + + builder.AddOrUpdate("Providers.MenuItems.FriendlyName.Entity", "Internal object or URL", "Internes Objekt oder URL"); + builder.AddOrUpdate("Providers.MenuItems.FriendlyName.Route", "Route", "Route"); + builder.AddOrUpdate("Providers.MenuItems.FriendlyName.Catalog", "Category tree", "Warengruppenbaum"); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.RouteName", "Route name", "Name der Route"); + builder.AddOrUpdate("Admin.ContentManagement.Menus.RouteValues", "Route values (JSON)", "Route Werte (JSON)"); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.SystemName", + "System name", + "Systemname", + "The system name of the menu.", + "Der Systemname des Mens."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Template", + "Design template", + "Design Vorlage", + "The template defines the way how the menu is displayed.", + "ber die Vorlage wird die Darstellungsart des Mens festgelegt."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.WidgetZone", + "Widget zone", + "Widget Zone", + "Specifies widget zones in which the menu should be displayed.", + "Legt Widget Zonen fest, in denen das Men dargestellt werden soll."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Title", + "Title", + "Titel", + "Specifies the title.", + "Legt den Titel fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Published", + "Published", + "Verffentlicht", + "Specifies whether the menu is visible in the shop.", + "Legt fest, ob das Men im Shop sichtbar ist."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.DisplayOrder", + "Display order", + "Reihenfolge", + "Specifies the display order of the widget zones.", + "Legt die Darstellungreihenfolge der Widget Zonen fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.ParentItem", + "Parent menu item", + "bergeordnetes Menelement", + "Specifies the parent menu item. Leave the field empty to create a first-level menu item.", + "Legt das bergeordnete Menelement fest. Lassen Sie das Feld leer, um ein Menelement erster Ebene zu erzeugen."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.LinkTarget", + "Target", + "Ziel", + "Specifies the link target.", + "Legt das Ziel des Links fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.ShortDescription", + "Short description", + "Kurzbeschreibung", + "Specifies a short description. Used as the 'title' attribute for the menu link.", + "Legt eine Kurzbeschreibung fest. Wird als 'title' Attribut fr das Menelement verwendet."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.PermissionNames", + "Required permissions", + "Erforderliche Rechte", + "Specifies access permissions that are required to display the menu item (at least 1 permission must be granted).", + "Legt Zugriffsrechte fest, die fr die Anzeige des Menelementes erforderlich sind (mind. 1 Recht muss gewhrt sein)."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.Published", + "Published", + "Verffentlicht", + "Specifies whether the menu item is visible in the shop.", + "Legt fest, ob das Menelement im Shop sichtbar ist."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.DisplayOrder", + "Display order", + "Reihenfolge", + "Specifies the order of the menu item within a menu level.", + "Legt die Reihenfolge des Menelements innerhalb einer Menebene fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.BeginGroup", + "Begin group", + "Gruppe beginnen", + "Inserts a separator before the link and optionally a heading (short description).", + "Fgt vor den Link ein Trennelement sowie optional eine berschrift ein (Kurzbeschreibung)."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.ShowExpanded", + "Show expanded", + "Geffnet anzeigen", + "If selected and this menu item has children, the menu will initially appear expanded.", + "Legt fest, ob das Men anfnglich geffnet ist, sofern es Kindelemente besitzt."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.NoFollow", + "nofollow", + "nofollow", + "Sets the HTML attribute rel='nofollow'.", + "Gibt das HTML-Attribut rel='nofollow' aus."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.NewWindow", + "Open in new browser tab", + "In neuem Browsertab ffnen"); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.Icon", + "Icon", + "Icon", + "Specifies an optional icon.", + "Legt ein optionales Icon fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.HtmlId", + "HTML ID", + "HTML ID", + "Sets the HTML ID attribute for the menu link.", + "Legt das HTML ID Attribut fr das Menelement fest."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.CssClass", + "CSS class", + "CSS Klasse", + "Sets a CSS class for the menu link.", + "Legt eine CSS Klasse fr das Menelement fest."); + + builder.Delete("Admin.Configuration.Settings.GeneralCommon.SocialSettings.GooglePlusLink"); + builder.Delete("Admin.Configuration.Settings.GeneralCommon.SocialSettings.GooglePlusLink.Hint"); + + builder.AddOrUpdate("Products.BasePriceInfo.LanguageInsensitive", + "{0} / {1} {2}", + "{0} / {1} {2}"); + + builder.AddOrUpdate("Common.Advanced", + "Advanced", + "Erweitert"); + + builder.AddOrUpdate("ShoppingCart.DiscountCouponCode.NoMoreDiscount", + "Further discounts are not possible.", + "Eine weitere Rabattierung ist nicht mglich."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.resx new file mode 100644 index 0000000000..47694659f0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905101159134_V320Resources.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/201905271110370_V321Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.Designer.cs new file mode 100644 index 0000000000..a194593e7c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.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 V321Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V321Resources)); + + string IMigrationMetadata.Id + { + get { return "201905271110370_V321Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.cs new file mode 100644 index 0000000000..89ade79f68 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.cs @@ -0,0 +1,35 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class V321Resources : 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(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + 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 verfgbaren Sprachen fr Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verfgbaren Ressourcen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.resx new file mode 100644 index 0000000000..eea8366a2c --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201905271110370_V321Resources.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/201906252008551_QuantityUnitNamePlural.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.Designer.cs new file mode 100644 index 0000000000..186f72e27d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.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 QuantityUnitNamePlural : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(QuantityUnitNamePlural)); + + string IMigrationMetadata.Id + { + get { return "201906252008551_QuantityUnitNamePlural"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.cs b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.cs new file mode 100644 index 0000000000..313530a024 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.cs @@ -0,0 +1,170 @@ +namespace SmartStore.Data.Migrations +{ + using System.Collections.Generic; + using System.Data.Entity.Migrations; + using System.Linq; + using SmartStore.Core.Data; + using SmartStore.Core.Domain.Directory; + using SmartStore.Core.Domain.Localization; + using SmartStore.Data.Setup; + + public partial class QuantityUnitNamePlural : DbMigration, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.QuantityUnit", "NamePlural", c => c.String(nullable: false, maxLength: 50, defaultValue: "" )); + } + + public override void Down() + { + DropColumn("dbo.QuantityUnit", "NamePlural"); + } + + public bool RollbackOnFailure => true; + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + context.SaveChanges(); + + if (!DataSettings.DatabaseIsInstalled()) + { + return; + } + + var quPluralDe = new Dictionary + { + { "Stck", "Stck" }, + { "Schachtel", "Schachteln" }, + { "Paket", "Pakete" }, + { "Palette", "Paletten" }, + { "Kiste", "Kisten" }, + { "Einheit", "Einheiten" }, + { "Sack", "Scke" }, + { "Tte", "Tten" }, + { "Dose", "Dosen" }, + { "Stange", "Stangen" }, + { "Flasche", "Flaschen" }, + { "Glas", "Glser" }, + { "Bund", "Bnde" }, + { "Rolle", "Rollen" }, + { "Fass", "Fsser" }, + { "Set", "Sets" } + }; + + var quPluralEn = new Dictionary + { + { "Piece", "Pieces" }, + { "Box", "Boxes" }, + { "Parcel", "Parcels" }, + { "Palette", "Pallets" }, + { "Unit", "Units" }, + { "Sack", "Sacks" }, + { "Bag", "Bags" }, + { "Can", "Cans" }, + { "Tin", "Tins" }, + { "Packet", "Packets" }, + { "Package", "Packages" }, + { "Bar", "Bars" }, + { "Bottle", "Bottles" }, + { "Glass", "Glasses" }, + { "Bunch", "Bunches" }, + { "Roll", "Rolls" }, + { "Cup", "Cups" }, + { "Bundle", "Bundles" }, + { "Barrel", "Barrels" }, + { "Set", "Sets" }, + { "Bucket", "Buckets" } + }; + + using (var scope = new DbContextScope(ctx: context, validateOnSave: false, hooksEnabled: false, autoCommit: false)) + { + var languages = context.Set().ToDictionary(x => x.Id, x => x); + var defaultLang = languages.Values.OrderBy(x => x.DisplayOrder).First(); + + // Quantity units. + var quantityUnits = context.Set().ToList(); + if (quantityUnits.Any()) + { + foreach (var quantityUnit in quantityUnits) + { + var name = quantityUnit.Name.EmptyNull(); + string namePlural = null; + + if (defaultLang.UniqueSeoCode.IsCaseInsensitiveEqual("de")) + { + quPluralDe.TryGetValue(name, out namePlural); + } + else if (defaultLang.UniqueSeoCode.IsCaseInsensitiveEqual("en")) + { + quPluralEn.TryGetValue(name, out namePlural); + } + + quantityUnit.NamePlural = namePlural.NullEmpty() ?? name; + } + + scope.Commit(); + } + + // Localized properties. + var lpSet = context.Set(); + var pluralCount = lpSet + .Where(x => x.LocaleKeyGroup == "QuantityUnit" && x.LocaleKey == "NamePlural") + .Count(); + + if (pluralCount == 0) + { + var localizedProperties = lpSet + .Where(x => x.LocaleKeyGroup == "QuantityUnit" && x.LocaleKey == "Name") + .ToList(); + + if (localizedProperties.Any()) + { + foreach (var lp in localizedProperties) + { + if (languages.TryGetValue(lp.LanguageId, out var language)) + { + var name = lp.LocaleValue.EmptyNull(); + string namePlural = null; + + if (language.UniqueSeoCode.IsCaseInsensitiveEqual("de")) + { + quPluralDe.TryGetValue(name, out namePlural); + } + else if (language.UniqueSeoCode.IsCaseInsensitiveEqual("en")) + { + quPluralEn.TryGetValue(name, out namePlural); + } + + if (namePlural.HasValue()) + { + lpSet.Add(new LocalizedProperty + { + EntityId = lp.EntityId, + LanguageId = lp.LanguageId, + LocaleKeyGroup = lp.LocaleKeyGroup, + LocaleKey = "NamePlural", + LocaleValue = namePlural + }); + } + } + } + + scope.Commit(); + } + } + } + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Plural", "Plural", "Mehrzahl"); + + builder.AddOrUpdate("Admin.Configuration.QuantityUnit.Fields.NamePlural", + "Name plural", + "Name Mehrzahl", + "Sets the name in plural. Example: \"Barrels\" for the unit \"Barrel\".", + "Legt den Namen in Mehrzahl fest. Beispiel: \"Fsser\" fr die Verpackungseinheit \"Fass\"."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.resx b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.resx new file mode 100644 index 0000000000..887e09fc9d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201906252008551_QuantityUnitNamePlural.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/201907032251575_CategoryExternalLink.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.Designer.cs new file mode 100644 index 0000000000..784b2cb7ce --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.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 CategoryExternalLink : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CategoryExternalLink)); + + string IMigrationMetadata.Id + { + get { return "201907032251575_CategoryExternalLink"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.cs b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.cs new file mode 100644 index 0000000000..708ca38b83 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.cs @@ -0,0 +1,18 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class CategoryExternalLink : DbMigration + { + public override void Up() + { + AddColumn("dbo.Category", "ExternalLink", c => c.String(maxLength: 255)); + } + + public override void Down() + { + DropColumn("dbo.Category", "ExternalLink"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.resx b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.resx new file mode 100644 index 0000000000..3a1de6db32 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201907032251575_CategoryExternalLink.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/201908150749388_V322Resources.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.Designer.cs new file mode 100644 index 0000000000..be8c91d1d2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.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 V322Resources : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(V322Resources)); + + string IMigrationMetadata.Id + { + get { return "201908150749388_V322Resources"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.cs b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.cs new file mode 100644 index 0000000000..12a38b031e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.cs @@ -0,0 +1,69 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class V322Resources : 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(); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + 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 verfgbaren Sprachen fr Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verfgbaren Ressourcen."); + + builder.AddOrUpdate("Checkout.OrderCompletes", + "Your order will be completed.", + "Ihre Bestellung wird abgeschlossen."); + + builder.AddOrUpdate("Admin.Catalog.Attributes.CheckoutAttributes.Fields.TextPrompt", + "Text prompt", + "Text Eingabeaufforderung", + "Specifies the prompt text.", + "Legt den Text zur Eingabeaufforderung fest."); + + builder.AddOrUpdate("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Fields.TextPrompt", + "Text prompt", + "Text Eingabeaufforderung"); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.ExternalLink", + "External link", + "Externer Link", + "Alternative external link for this category in the main menu and in category listings. For example, to a landing page that contains a back link to the category.", + "Abweichender, externer Verweis fr diese Warengruppe im Hauptmen und in Warengruppen-Listings. Z.B. auf eine Landingpage, die einen Rckverweis auf die Warengruppe enthlt."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Title", + "Title", + "Titel", + "Specifies the title. Please keep in mind that this title is not displayed in all design templates.", + "Legt den Titel fest. Bitte beachten Sie, dass dieser Titel nicht bei allen Design-Vorlagen ausgegeben wird."); + + builder.AddOrUpdate("Admin.ContentManagement.Menus.Item.InvalidRouteValues", + "Please check the link data. No link can be created on the basis of your input.", + "Bitte berprfen Sie die Link-Daten. Auf Basis Ihrer Eingabe kann kein Link erzeugt werden."); + + builder.AddOrUpdate("Admin.Packaging.IsIncompatible", + "The package is not compatible the current app version {0}. Please update Smartstore.NET or install another version of this package.", + "Das Paket ist nicht kompatibel mit der aktuellen Programmversion {0}. Bitte aktualisieren Sie Smartstore.NET oder nutzen Sie eine andere, kompatible Paket-Version."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.resx b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.resx new file mode 100644 index 0000000000..a3562fc0f2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201908150749388_V322Resources.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 9b6b3b7efb..18a610b862 100644 --- a/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs +++ b/src/Libraries/SmartStore.Data/Migrations/MigrationsConfiguration.cs @@ -1,14 +1,12 @@ -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.Core.Data; + using SmartStore.Core.Domain.Catalog; + using SmartStore.Core.Domain.Common; using SmartStore.Utilities; - using SmartStore.Core.Domain.Media; - using Core.Domain.Configuration; - using SmartStore.Core.Domain.Customers; public sealed class MigrationsConfiguration : DbMigrationsConfiguration { @@ -17,270 +15,42 @@ public MigrationsConfiguration() AutomaticMigrationsEnabled = false; AutomaticMigrationDataLossAllowed = true; ContextKey = "SmartStore.Core"; + + if (DataSettings.Current.IsSqlServer) + { + var commandTimeout = CommonHelper.GetAppSetting("sm:EfMigrationsCommandTimeout"); + if (commandTimeout.HasValue) + { + CommandTimeout = commandTimeout.Value; + } + + CommandTimeout = 9999999; + } } public void SeedDatabase(SmartObjectContext context) { - Seed(context); + using (var scope = new DbContextScope(context, hooksEnabled: false)) + { + Seed(context); + scope.Commit(); + } } protected override 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"; - } } 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-Eintrgen der obersten Hierarchie fest, die im Katalogmen angezeigt werden. Alle weiteren Menu-Eintrge 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 verfgbaren 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 verfgbaren Sprachen fr Version {0} gefunden. Auf translate.smartstore.com finden Sie weitere Details zu verfgbaren Ressourcen."); - - builder.AddOrUpdate("Admin.Configuration.Languages.InstalledLanguages", - "Installed Languages", - "Installierte Sprachen"); - builder.AddOrUpdate("Admin.Configuration.Languages.AvailableLanguages", - "Available Languages", - "Verfgbare 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 verfgbaren 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 verffentlichte Sprache ist erforderlich."); - - builder.AddOrUpdate("Admin.Configuration.Languages.Fields.AvailableLanguageSetId", - "Available Languages", - "Verfgbare Sprachen", - "Specifies the available language whose localized resources are to be imported.", - "Legt die verfgbare 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 whlen Sie eine verfgbare Sprache, deren Ressourcen importiert werden sollen."); - - builder.AddOrUpdate("Admin.Configuration.Settings.Shipping.ChargeOnlyHighestProductShippingSurcharge", - "Charge the highest shipping surcharge only", - "Nur den hchsten Transportzuschlag berechnen", - "Specifies whether to charge only the highest additional shipping surcharge of products.", - "Bestimmt ob bei der Berechnung der Versandkosten nur der hchste Transportzuschlag von Produkten bercksichtigt 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 fr 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 fr 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 knnen die Felder Name, SKU und Kurzbeschreibung durchsucht werden. Fr 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 gehrenden 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 fhren 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 mchten."); - - builder.Delete("Account.PasswordRecoveryConfirm.InvalidEmail"); - builder.Delete("Account.PasswordRecoveryConfirm.InvalidToken"); - - builder.AddOrUpdate("Admin.Common.Acl.SubjectTo", - "Restrict access", - "Zugriff einschrnken", - "Determines whether this entity is subject to access restrictions (no = no restriction, yes = accessible only for selected customer groups)", - "Legt fest, ob dieser Datensatz Zugriffsbeschrnkungen unterliegt (Nein = keine Beschrnkung, Ja = zugnglich nur fr gewhlte 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.", - "Whlen Sie Kundengruppen, die auf den Datensatz zugreifen knnen. 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"); - - 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 gewhlte Zahlart muss hierfr Buchungen untersttzen."); - } - } + } + } } diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs index b870ca862c..3faed3ad16 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.SaveChanges.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Validation; using System.Linq; @@ -9,9 +8,7 @@ using System.Threading; using System.Threading.Tasks; using SmartStore.Core; -using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; -using SmartStore.Core.Infrastructure; using SmartStore.Utilities; using EfState = System.Data.Entity.EntityState; @@ -157,7 +154,7 @@ private bool IsHookableEntry(IHookedEntity entry) return IsHookableEntityType(entry.EntityType); } - private bool IsHookableEntityType(Type entityType) + private static bool IsHookableEntityType(Type entityType) { var isHookable = _hookableEntities.GetOrAdd(entityType, t => { @@ -197,55 +194,7 @@ public SaveStage Stage get { return _stage; } } - public int Execute() - { - var autoDetectChanges = _ctx.Configuration.AutoDetectChangesEnabled; - IEnumerable mergeableEntities = null; - - Action endExecute = () => - { - _ctx.Configuration.AutoDetectChangesEnabled = autoDetectChanges; - _ctx.IgnoreMergedData(mergeableEntities, false); - }; - - using (new ActionDisposable(endExecute)) - { - // Suppress implicit DetectChanges() calls by EF, - // e.g. called by SaveChanges(), ChangeTracker.Entries() etc. - _ctx.Configuration.AutoDetectChangesEnabled = false; - - // Get all attached entries implementing IMergedData, - // we need to ignore merge on them. Otherwise - // EF's change detection may think that properties has changed - // where they actually didn't. - mergeableEntities = _ctx.GetMergeableEntitiesFromChangeTracker().ToArray(); - - // Now ignore merged data, otherwise merged data will be saved to database - _ctx.IgnoreMergedData(mergeableEntities, true); - - // We must detect changes earlier in the process - // before hooks are executed. Therefore we suppressed the - // implicit DetectChanges() call by EF and call it here explicitly. - _ctx.ChangeTracker.DetectChanges(); - - // Now get changed entries - _changedEntries = _ctx.GetChangedEntries(); - - // pre - IEnumerable changedHookEntries; - PreExecute(out changedHookEntries); - - // save - var result = _ctx.SaveChangesCore(); - - // post - PostExecute(changedHookEntries); - - return result; - } - } - - public Task ExecuteAsync(CancellationToken cancellationToken) + private IDisposable ExecuteCore() { var autoDetectChanges = _ctx.Configuration.AutoDetectChangesEnabled; IEnumerable mergeableEntities = null; @@ -265,32 +214,46 @@ public Task ExecuteAsync(CancellationToken cancellationToken) // We must detect changes earlier in the process // before hooks are executed. Therefore we suppressed the - // implicit DetectChanges() calls by EF and call it here explicitly. + // implicit DetectChanges() call by EF and call it here explicitly. _ctx.ChangeTracker.DetectChanges(); // Now get changed entries _changedEntries = _ctx.GetChangedEntries(); // pre - IEnumerable changedHookEntries; - PreExecute(out changedHookEntries); + PreExecute(out IEnumerable changedHookEntries); - // save - var result = _ctx.SaveChangesCoreAsync(cancellationToken); + return new ActionDisposable(endExecute); - // post - result.ContinueWith((t) => + void endExecute() { - if (!t.IsFaulted) + try { + // post PostExecute(changedHookEntries); } + finally + { + _ctx.Configuration.AutoDetectChangesEnabled = autoDetectChanges; + _ctx.IgnoreMergedData(mergeableEntities, false); + } + } + } - _ctx.Configuration.AutoDetectChangesEnabled = autoDetectChanges; - _ctx.IgnoreMergedData(mergeableEntities, false); - }); + public int Execute() + { + using (ExecuteCore()) + { + return _ctx.SaveChangesCore(); + } + } - return result; + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + using (ExecuteCore()) + { + return await _ctx.SaveChangesCoreAsync(cancellationToken); + } } private IEnumerable PreExecute(out IEnumerable changedHookEntries) @@ -318,9 +281,9 @@ private IEnumerable PreExecute(out IEnumerable chang { changedHookEntries = _changedEntries .Select(x => new HookedEntity(x)) - .Where(x => _ctx.IsHookableEntityType(x.EntityType)) + .Where(x => IsHookableEntityType(x.EntityType)) .ToArray(); - + // Regardless of validation (possible fixing validation errors too) processedHooks = _hookHandler.TriggerPreSaveHooks(changedHookEntries, importantHooksOnly, out anyStateChanged); diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index dbc79bead9..ea5675eabb 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -11,6 +11,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; using SmartStore.Data.Setup; +using SmartStore.Utilities; using EfState = System.Data.Entity.EntityState; namespace SmartStore.Data @@ -18,8 +19,9 @@ namespace SmartStore.Data [DbConfigurationType(typeof(SmartDbConfiguration))] public abstract partial class ObjectContextBase : DbContext, IDbContext { - private static bool? s_isSqlServer2012OrHigher = null; - + private static bool? _isSqlServer2012OrHigher = null; + private static int? _commandTimeoutInSeconds = CommonHelper.GetAppSetting("sm:EfCommandTimeout"); + // Instance of the internal ObjectStateManager.TransactionManager // required for detecting if EF performs change detection private object _transactionManager; @@ -32,7 +34,7 @@ protected ObjectContextBase() { } - protected ObjectContextBase(string nameOrConnectionString, string alias = null) + protected ObjectContextBase(string nameOrConnectionString, string alias = null) : base(nameOrConnectionString) { this.HooksEnabled = true; @@ -40,28 +42,12 @@ protected ObjectContextBase(string nameOrConnectionString, string alias = null) this.Alias = null; this.DbHookHandler = NullDbHookHandler.Instance; - if (DataSettings.DatabaseIsInstalled() && !DbSeedingMigrator.IsMigrating) + if (_commandTimeoutInSeconds >= 0 && DataSettings.Current.IsSqlServer) { - //// listen to 'ObjectMaterialized' for load hooking - ((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += ObjectMaterialized; + Database.CommandTimeout = _commandTimeoutInSeconds; } } - private void ObjectMaterialized(object sender, ObjectMaterializedEventArgs e) - { - var entity = e.Entity as BaseEntity; - if (entity == null) - return; - - var hookHandler = this.DbHookHandler; - var importantHooksOnly = !this.HooksEnabled && hookHandler.HasImportantLoadHooks(); - - if (IsHookableEntityType(entity.GetUnproxiedType())) - { - hookHandler.TriggerLoadHooks(entity, importantHooksOnly); - } - } - public bool HooksEnabled { get; @@ -182,13 +168,6 @@ public IEnumerable SqlQuery(string sql, params object[] para return this.Database.SqlQuery(sql, parameters); } - /// - /// Executes the given DDL/DML command against the database. - /// - /// The command string - /// Timeout value, in seconds. A null value indicates that the default value of the underlying provider will be used - /// The parameters to apply to the command string. - /// The result returned by the database after executing the command. public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters) { Guard.NotEmpty(sql, "sql"); @@ -381,7 +360,7 @@ public static string GetConnectionString() protected internal bool IsSqlServer2012OrHigher() { - if (!s_isSqlServer2012OrHigher.HasValue) + if (!_isSqlServer2012OrHigher.HasValue) { try { @@ -390,15 +369,15 @@ protected internal bool IsSqlServer2012OrHigher() var info = this.GetSqlServerInfo(); string productVersion = info.ProductVersion; int version = productVersion.Split(new char[] { '.' })[0].ToInt(); - s_isSqlServer2012OrHigher = version >= 11; + _isSqlServer2012OrHigher = version >= 11; } catch { - s_isSqlServer2012OrHigher = false; + _isSqlServer2012OrHigher = false; } } - return s_isSqlServer2012OrHigher.Value; + return _isSqlServer2012OrHigher.Value; } public TEntity Attach(TEntity entity) where TEntity : BaseEntity diff --git a/src/Libraries/SmartStore.Data/Properties/AssemblyInfo.cs b/src/Libraries/SmartStore.Data/Properties/AssemblyInfo.cs index 11132fd2a1..31d6040e23 100644 --- a/src/Libraries/SmartStore.Data/Properties/AssemblyInfo.cs +++ b/src/Libraries/SmartStore.Data/Properties/AssemblyInfo.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Runtime.CompilerServices; + [assembly: AssemblyTitle("SmartStore.Data")] [assembly: InternalsVisibleTo("SmartStore.Web")] [assembly: InternalsVisibleTo("SmartStore.Data.Tests")] 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/MigrateDatabaseInitializer.cs b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs index 3c3e6a19c2..7042551b5c 100644 --- a/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs +++ b/src/Libraries/SmartStore.Data/Setup/MigrateDatabaseInitializer.cs @@ -4,7 +4,10 @@ using System.Data.Entity.Infrastructure; using System.Data.Entity.Migrations; using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Data.Caching; using SmartStore.Data.Migrations; +using SmartStore.Utilities; namespace SmartStore.Data.Setup { @@ -16,7 +19,6 @@ public class MigrateDatabaseInitializer : IDatabaseInitialize where TContext : DbContext, new() where TConfig : DbMigrationsConfiguration, new() { - #region Ctor public MigrateDatabaseInitializer() @@ -86,27 +88,31 @@ public virtual void InitializeDatabase(TContext context) } } - // run all pending migrations - var appliedCount = migrator.RunPendingMigrations(context); - - if (appliedCount > 0) - { - Seed(context); - } - else + using (new DbContextScope(context as IDbContext, hooksEnabled: false)) { - var coreConfig = config as MigrationsConfiguration; - if (coreConfig != null && context is SmartObjectContext) + // run all pending migrations + var appliedCount = migrator.RunPendingMigrations(context); + + if (appliedCount > 0) + { + Seed(context); + } + else { // DB is up-to-date and no migration ran. - // Call the main Seed method anyway (on every startup), - // we could have locale resources or settings to add/update. - coreConfig.SeedDatabase(context as SmartObjectContext); + EfMappingViewCacheFactory.SetContext(context); + + if (config is MigrationsConfiguration coreConfig && context is SmartObjectContext ctx) + { + // Call the main Seed method anyway (on every startup), + // we could have locale resources or settings to add/update. + coreConfig.SeedDatabase(ctx); + } } - } - // not needed anymore - this.DataSeeders = null; + // not needed anymore + this.DataSeeders = null; + } } #endregion @@ -134,6 +140,15 @@ protected virtual TConfig CreateConfiguration() config.TargetDatabase = new DbConnectionInfo(this.ConnectionString, dbContextInfo.ConnectionProviderName); } + if (config.CommandTimeout == null && DataSettings.Current.IsSqlServer) + { + var commandTimeout = CommonHelper.GetAppSetting("sm:EfMigrationsCommandTimeout"); + if (commandTimeout.HasValue) + { + config.CommandTimeout = commandTimeout.Value; + } + } + return config; } diff --git a/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs b/src/Libraries/SmartStore.Data/Setup/SeedData/InvariantSeedData.cs index 00542888ba..25ec8fc33c 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("~/App_Data/Samples/"); - this._sampleDownloadsPath = CommonHelper.MapPath("~/App_Data/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; @@ -208,42 +202,42 @@ public IList MeasureWeights() protected virtual string TaxNameBooks { - get { return "Books"; } + get => "Books"; } protected virtual string TaxNameDigitalGoods { - get { return "Downloadable Products"; } + get => "Downloadable Products"; } protected virtual string TaxNameJewelry { - get { return "Jewelry"; } + get => "Jewelry"; } protected virtual string TaxNameApparel { - get { return "Apparel & Shoes"; } + get => "Apparel & Shoes"; } protected virtual string TaxNameFood { - get { return "Food"; } + get => "Food"; } protected virtual string TaxNameElectronics { - get { return "Electronics & Software"; } + get => "Electronics & Software"; } protected virtual string TaxNameTaxFree { - get { return "Tax free"; } + get => "Tax free"; } public virtual decimal[] FixedTaxRates { - get { return new decimal[] { 0, 0, 0, 0, 0 }; } + get => new decimal[] { 0, 0, 0, 0, 0 }; } public IList TaxCategories() @@ -3879,6 +3873,8 @@ public IList Topics() SystemName = "CheckoutAsGuestOrRegister", IncludeInSitemap = false, IsPasswordProtected = false, + RenderAsWidget = true, + WidgetWrapContent = false, Title = "", Body = "

Register and save time!
Register with us for future convenience:

  • Fast and easy check out
  • Easy access to your order history and status
" }, @@ -3895,7 +3891,9 @@ public IList Topics() SystemName = "ContactUs", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "", + RenderAsWidget = true, + WidgetWrapContent = false, + Title = "Contact us", Body = "

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

" }, new Topic @@ -3903,7 +3901,9 @@ public IList Topics() SystemName = "ForumWelcomeMessage", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "Forums", + RenderAsWidget = true, + WidgetWrapContent = false, + Title = "Forums", Body = "

Put your welcome message here. You can edit this in the admin site.

" }, new Topic @@ -3911,7 +3911,9 @@ public IList Topics() SystemName = "HomePageText", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "Welcome to our store", + RenderAsWidget = true, + WidgetWrapContent = false, + Title = "Welcome to our store", Body = "

Online shopping is the process consumers go through to purchase products or services over the Internet. You can edit this in the admin site.

" }, new Topic @@ -3919,7 +3921,9 @@ public IList Topics() SystemName = "LoginRegistrationInfo", IncludeInSitemap = false, IsPasswordProtected = false, - Title = "About login / registration", + RenderAsWidget = true, + WidgetWrapContent = false, + Title = "About login / registration", Body = "

Not registered yet?

Create your own account now and experience our diversity. With an account you can place orders faster and will always have a perfect overview of your current and previous orders.

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

" }, @@ -3939,7 +3944,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", @@ -3971,6 +3975,11 @@ public IList Topics() public IList Settings() { + var defaultDimensionId = _ctx.Set().FirstOrDefault(x => x.SystemKeyword == "inch")?.Id ?? 0; + var defaultWeightId = _ctx.Set().FirstOrDefault(x => x.SystemKeyword == "lb")?.Id ?? 0; + var defaultLanguageId = _ctx.Set().FirstOrDefault()?.Id ?? 0; + var defaultEmailAccountId = _ctx.Set().FirstOrDefault()?.Id ?? 0; + var entities = new List { new PdfSettings @@ -3979,63 +3988,60 @@ public IList Settings() new CommonSettings { }, - new SeoSettings() - { - }, - new SocialSettings() + new SeoSettings { }, - new AdminAreaSettings() + new SocialSettings { }, - new CatalogSettings() + new AdminAreaSettings { }, - new LocalizationSettings() + new CatalogSettings { - DefaultAdminLanguageId = _ctx.Set().First().Id }, - new CustomerSettings() + new LocalizationSettings { + DefaultAdminLanguageId = defaultLanguageId }, - new AddressSettings() + new CustomerSettings { }, - new MediaSettings() + new AddressSettings { }, - new StoreInformationSettings() + new MediaSettings { }, - new RewardPointsSettings() + new StoreInformationSettings { }, - new CurrencySettings() + new RewardPointsSettings { }, - new MeasureSettings() + new CurrencySettings { - BaseDimensionId = _ctx.Set().Where(m => m.SystemKeyword == "inch").Single().Id, - BaseWeightId = _ctx.Set().Where(m => m.SystemKeyword == "lb").Single().Id, }, - new MessageTemplatesSettings() + new MeasureSettings { + BaseDimensionId = defaultDimensionId, + BaseWeightId = defaultWeightId, }, - new ShoppingCartSettings() + new ShoppingCartSettings { }, - new OrderSettings() + new OrderSettings { }, - new SecuritySettings() + new SecuritySettings { }, - new ShippingSettings() + new ShippingSettings { }, - new PaymentSettings() + new PaymentSettings { - ActivePaymentMethodSystemNames = new List() + ActivePaymentMethodSystemNames = new List { "Payments.CashOnDelivery", "Payments.Manual", @@ -4043,23 +4049,23 @@ public IList Settings() "Payments.Prepayment" } }, - new TaxSettings() + new TaxSettings { }, - new BlogSettings() + new BlogSettings { }, - new NewsSettings() + new NewsSettings { }, - new ForumSettings() + new ForumSettings { }, - new EmailAccountSettings() + new EmailAccountSettings { - DefaultEmailAccountId = _ctx.Set().First().Id + DefaultEmailAccountId = defaultEmailAccountId }, - new ThemeSettings() + new ThemeSettings { } }; @@ -7525,7 +7531,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 +7550,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 +7569,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 +7588,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 +7609,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 +7628,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 +7648,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 +7667,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 +7686,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 +7705,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 +7725,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 +7744,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 @@ -8908,7 +8914,7 @@ public IList CategoriesFirstLevel() Name = "Apple", Alias = "Apple", CategoryTemplateId = categoryTemplateInGridAndLines.Id, - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/jpeg", GetSeName("Apple")), + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName("Apple")), Published = true, DisplayOrder = 1, MetaTitle = "Apple", @@ -9044,6 +9050,77 @@ public IList CategoriesSecondLevel() #region category definitions + #region new + + var categoryFashionJackets = new Category + { + Name = "Jackets", + Alias = "Jackets", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_jackets.jpg"), "image/png", GetSeName("Jackets")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Jackets", + ShowOnHomePage = true + }; + + var categoryFashionLeatherJackets = new Category + { + Name = "Leather jackets", + Alias = "Leather jackets", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_leather_jackets.jpg"), "image/png", GetSeName("Leather jackets")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Leather jackets", + ShowOnHomePage = true + }; + + var categoryFashionShoes = new Category + { + Name = "Shoes", + Alias = "Shoes", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_shoes.png"), "image/png", GetSeName("Shoes")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Shoes", + ShowOnHomePage = true + }; + + var categoryFashionTrousers = new Category + { + Name = "Trousers", + Alias = "Pants", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_trousers.png"), "image/png", GetSeName("Trousers")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Trousers", + ShowOnHomePage = true + }; + + var categoryFashionTracksuits = new Category + { + Name = "Tracksuits", + Alias = "Tracksuits", + CategoryTemplateId = categoryTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_tracksuit.png"), "image/png", GetSeName("Tracksuits")), + Published = true, + ParentCategoryId = _ctx.Set().Where(x => x.MetaTitle == "Fashion").First().Id, + DisplayOrder = 1, + MetaTitle = "Tracksuits", + ShowOnHomePage = true + }; + + #endregion + + + var categorySportsGolf = new Category { Name = "Golf", @@ -9173,7 +9250,8 @@ public IList CategoriesSecondLevel() var entities = new List { categorySportsSunglasses,categorySportsSoccer, categorySportsBasketball,categorySportsGolf, categoryBooksSpiegel, categoryBooksCookAndEnjoy, - categoryGamingAccessories, categoryGamingGames + categoryGamingAccessories, categoryGamingGames, categoryFashionJackets, categoryFashionLeatherJackets, categoryFashionShoes, categoryFashionTrousers, + categoryFashionTracksuits }; this.Alter(entities); @@ -9191,11 +9269,37 @@ public IList Manufacturers() //var categoryTemplateInGridAndLines = // this.CategoryTemplates().Where(pt => pt.Name == "Products in Grid or Lines").FirstOrDefault(); - //categories + // + + #region Jack Wolfskin + + var manufacturerJackWolfskin = new Manufacturer + { + Name = "Jack-Wolfskin", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_Jack_Wolfskin.png"), "image/png", GetSeName("Jack Wolfskin")), + Published = true, + DisplayOrder = 1 + }; + + #endregion + + #region Mey & Edlich + + var manufacturerMeyAndEdlich = new Manufacturer + { + Name = "Mey-And-Edlich", + ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, + Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "manufacturer_MeyAndEdlich.jpg"), "image/png", GetSeName("Mey Edlich")), + Published = true, + DisplayOrder = 1 + }; + #endregion + #region EA Sports - var manufacturerWarnerHome = new Manufacturer + var manufacturerEASports = new Manufacturer { Name = "EA Sports", ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, @@ -9208,7 +9312,7 @@ public IList Manufacturers() #region Warner Home Video Games - var manufacturerEASports = new Manufacturer + var manufacturerWarnerHome = new Manufacturer { Name = "Warner Home Video Games", ManufacturerTemplateId = manufacturerTemplateInGridAndLines.Id, @@ -9610,17 +9714,19 @@ public IList Manufacturers() #endregion Ubisoft var entities = new List - { - manufacturerEASports,manufacturerWarnerHome,manufacturerBreitling,manufacturerTissot,manufacturerSeiko, manufacturerTitleist,manufacturerApple,manufacturerSamsung,manufacturerLG,manufacturerTrekStor, manufacturerWesternDigital,manufacturerDell, manufacturerMSI, + { + manufacturerEASports,manufacturerWarnerHome,manufacturerBreitling,manufacturerTissot,manufacturerSeiko, manufacturerTitleist, manufacturerApple, + manufacturerSamsung,manufacturerLG,manufacturerTrekStor, manufacturerWesternDigital,manufacturerDell, manufacturerMSI, manufacturerCanon, manufacturerCasio, manufacturerPanasonic, manufacturerBlackBerry, manufacturerHTC, manufacturerFestina, manufacturerCertina, - manufacturerHP, manufacturerAcer, manufacturerSony, manufacturerUbisoft,manufacturerOakley,manufacturerRayban,manufacturerAdidas, manufacturerWilson,manufacturerPuma,manufacturerNike + manufacturerHP, manufacturerAcer, manufacturerSony, manufacturerUbisoft, manufacturerOakley, manufacturerRayban, manufacturerAdidas, manufacturerWilson, + manufacturerPuma,manufacturerNike, manufacturerMeyAndEdlich, manufacturerJackWolfskin, manufacturerMicrosoft }; this.Alter(entities); return entities; } - private List GetFashionProducts() + private List GetFashionProducts(Dictionary specAttributes) { var result = new List(); var productTemplateSimple = _ctx.Set().First(x => x.ViewPath == "Product"); @@ -9677,7 +9783,7 @@ private List GetFashionProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionCotton + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) }); result.Add(converseAllStar); @@ -9725,8 +9831,8 @@ private List GetFashionProducts() Price = 29.00M }); - var shirtMeccanicaImages = new string[] { "product_shirt_meccanica_red_1.jpg", "product_shirt_meccanica_red_2.jpg", "product_shirt_meccanica_red_3.jpg", - "product_shirt_meccanica_red_4.jpg", "product_shirt_meccanica_black_1.jpg", "product_shirt_meccanica_black_2.jpg", "product_shirt_meccanica_black_3.jpg" }; + var shirtMeccanicaImages = new string[] { "product_shirt_meccanica_black_1.jpg", "product_shirt_meccanica_black_2.jpg", "product_shirt_meccanica_black_3.jpg", + "product_shirt_meccanica_red_1.jpg", "product_shirt_meccanica_red_2.jpg", "product_shirt_meccanica_red_3.jpg", "product_shirt_meccanica_red_4.jpg" }; for (var i = 0; i < shirtMeccanicaImages.Length; ++i) { @@ -9742,8 +9848,8 @@ private List GetFashionProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionCotton - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); result.Add(shirtMeccanica); @@ -9782,8 +9888,8 @@ private List GetFashionProducts() DisplayOrder = 1 }); - var ladiesJacketImages = new string[] { "product_ladies_jacket_red.jpg", "product_ladies_jacket_orange.jpg", "product_ladies_jacket_green.jpg", - "product_ladies_jacket_blue.jpg", "product_ladies_jacket_navy.jpg", "product_ladies_jacket_silver.jpg", "product_ladies_jacket_black.jpg" }; + var ladiesJacketImages = new string[] { "product_ladies_jacket_silver.jpg", "product_ladies_jacket_black.jpg", "product_ladies_jacket_red.jpg", + "product_ladies_jacket_orange.jpg", "product_ladies_jacket_green.jpg", "product_ladies_jacket_blue.jpg", "product_ladies_jacket_navy.jpg", }; for (var i = 0; i < ladiesJacketImages.Length; ++i) { @@ -9799,7 +9905,7 @@ private List GetFashionProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 11) + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 11) }); result.Add(ladiesJacket); @@ -9845,30 +9951,23 @@ private List GetFashionProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionCotton - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); result.Add(clarkJeans); - - return result; + return result; } - private List GetFurnitureProducts() + private List GetFurnitureProducts(Dictionary specAttributes) { var result = new List(); var productTemplateSimple = _ctx.Set().First(x => x.ViewPath == "Product"); var thirdDeliveryTime = _ctx.Set().First(x => x.DisplayOrder == 2); var furnitureCategory = _ctx.Set().First(x => x.MetaTitle == "Furniture"); - var specOptionLeather = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 5); - var specOptionWood = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 13); - var specOptionPlastic = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 3); - var specOptionGlass = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 14); - var specOptionSteel = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 1); - var specOptionAluminium = _ctx.Set().First(x => x.DisplayOrder == 8).SpecificationAttributeOptions.First(x => x.DisplayOrder == 4); - // Le Corbusier LC 6 table - var corbusierTable = new Product + // Le Corbusier LC 6 table + var corbusierTable = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -9925,15 +10024,15 @@ private List GetFurnitureProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionSteel - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); corbusierTable.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, - SpecificationAttributeOption = specOptionGlass - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 14) + }); result.Add(corbusierTable); @@ -9993,15 +10092,15 @@ private List GetFurnitureProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionPlastic - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) + }); ballChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, - SpecificationAttributeOption = specOptionLeather - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); result.Add(ballChair); @@ -10069,21 +10168,21 @@ private List GetFurnitureProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionWood - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 13) + }); loungeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, - SpecificationAttributeOption = specOptionLeather - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); loungeChair.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, - SpecificationAttributeOption = specOptionAluminium + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 4) }); result.Add(loungeChair); @@ -10141,40 +10240,158 @@ private List GetFurnitureProducts() AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, - SpecificationAttributeOption = specOptionLeather - }); + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) + }); result.Add(cubeChair); return result; } - public IList Products() - { - #region definitions + public IList Products() + { + var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); - // Pictures - var sampleImagesPath = this._sampleImagesPath; + var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); + var firstDeliveryTime = _ctx.Set().First(x => x.DisplayOrder == 0); + var secondDeliveryTime = _ctx.Set().First(x => x.DisplayOrder == 1); + var thirdDeliveryTime = _ctx.Set().First(x => x.DisplayOrder == 2); - // Downloads - var sampleDownloadsPath = this._sampleDownloadsPath; + var manufacturers = _ctx.Set().ToList().ToDictionarySafe(x => x.Name, x => x); + var categories = _ctx.Set().ToList().ToDictionarySafe(x => x.Alias, x => x); + var specAttributes = _ctx.Set().ToList().ToDictionarySafe(x => x.DisplayOrder, x => x); - // Templates - var productTemplate = _ctx.Set().First(x => x.ViewPath == "Product"); - var firstDeliveryTime = _ctx.Set().First(sa => sa.DisplayOrder == 0); + //#region category shoes - var specialPriceEndDate = DateTime.UtcNow.AddMonths(1); + //var categoryShoes = _ctx.Set().First(x => x.Alias == "Shoes"); - #endregion definitions + //#region product Jack Wolfskin COOGEE LOW M - #region category golf + //var productJackWolfskinCooGeeLowM = new Product + //{ + // ProductType = ProductType.SimpleProduct, + // VisibleIndividually = true, + // Name = "COOGEE LOW M", + // MetaTitle = "COOGEE LOW M", + // ShortDescription = "MÄNNER FREIZEITSCHUHE", + // FullDescription = "

Du bist immer auf dem Sprung: zum Kino, zur neueröffneten Bar oder zum nächsten Stadtfest. Der stylische COOGEE LOW ist DER Schuh für dein Leben in der Stadt. Denn er verbindet Funktion mit Style. Die sportliche EVA - Zwischensohle ist nicht nur leicht und flexibel, sondern dämpft deine Ferse mit ihrer Erhöhung im hinteren Bereich auch besonders gut und ist sehr komfortabel.So sind deine Füße auch nach vielen Stunden noch fit – du wirst es merken, wenn nach einem langen Tag z.B.noch eine spontane Party ansteht. Die Passform haben wir nach dem Vorbild einer Socke gestaltet, sie ist locker und komfortabel.Dennoch bleibt der Schuh in seiner schmalen Form. Dank des atmungsaktiven Obermaterials bleibt das Fußklima angenehm, egal, wie warm es draussen ist.Gleichzeitig ist der Schuh sehr pflegeleicht.Worauf wartest du also noch ? Ab in die City!

", + // Sku = "Wolfskin-4032541", + // ProductTemplateId = productTemplate.Id, + // AllowCustomerReviews = true, + // Published = true, + // Price = 69.90M, + // OldPrice = 99.95M, + // ManageInventoryMethod = ManageInventoryMethod.ManageStock, + // OrderMinimumQuantity = 1, + // OrderMaximumQuantity = 10000, + // StockQuantity = 10000, + // NotifyAdminForQuantityBelow = 1, + // IsShipEnabled = true, + // DeliveryTime = firstDeliveryTime, + // DisplayOrder = 5 + //}; + + //AddProductPicture(productJackWolfskinCooGeeLowM, "product_wolfskin_shoes_coogee_1.jpg", "jack-wolfskin-shoes-coogee-low-m-1"); + + //productJackWolfskinCooGeeLowM.ProductCategories.Add(new ProductCategory { Category = categoryShoes, DisplayOrder = 1 }); + + //productJackWolfskinCooGeeLowM.ProductManufacturers.Add(new ProductManufacturer + //{ + // Manufacturer = _ctx.Set().Where(c => c.Name == "Jack-Wolfskin").Single(), + // DisplayOrder = 1, + //}); + + //#endregion product Jack Wolfskin COOGEE LOW M + + //#region product Adidas SUPERSTAR SCHUH + + //var productAdidasSuperstarSchuh = new Product + //{ + // ProductType = ProductType.SimpleProduct, + // VisibleIndividually = true, + // Name = "SUPERSTAR SCHUH", + // MetaTitle = "SUPERSTAR SCHUH", + // ShortDescription = "DER STREETWEAR-KLASSIKER MIT DER SHELL TOE.", + // FullDescription = "

Der adidas Superstar wurde erstmals 1969 herausgebracht und machte seinem Namen schon bald alle Ehre. Heute gilt er als Streetstyle-Legende. In dieser Version kommt der Schuh mit einem bequemen Obermaterial aus Full-Grain-Leder. Perfekt wird der Look durch die klassische Shell Toe aus Gummi für mehr Strapazierfähigkeit.

", + // Sku = "Adidas-C77124", + // ProductTemplateId = productTemplate.Id, + // AllowCustomerReviews = true, + // Published = true, + // Price = 99.95M, + // ManageInventoryMethod = ManageInventoryMethod.ManageStock, + // OrderMinimumQuantity = 1, + // OrderMaximumQuantity = 10000, + // StockQuantity = 10000, + // NotifyAdminForQuantityBelow = 1, + // IsShipEnabled = true, + // DeliveryTime = firstDeliveryTime, + // DisplayOrder = 5 + //}; + + //AddProductPicture(productAdidasSuperstarSchuh, "product_adidas_superstar_schuh_1.jpg", "adidas-superstar-schuh-1"); + + //productJackWolfskinCooGeeLowM.ProductCategories.Add(new ProductCategory { Category = categoryShoes, DisplayOrder = 1 }); + + //productJackWolfskinCooGeeLowM.ProductManufacturers.Add(new ProductManufacturer + //{ + // Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), + // DisplayOrder = 1, + //}); + + + //#endregion product Adidas SUPERSTAR SCHUH + //#endregion category shoes + + #region category jackets + + var categoryJackets = _ctx.Set().First(x => x.Alias == "Jackets"); + + #region product Jack Wolfskin KANUKA POINT + + var productJackWolfsKinKanukaPoint = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "KANUKA POINT JACKET M", + MetaTitle = "KANUKA POINT JACKET M", + ShortDescription = "SOFTSHELLJACKE MÄNNER", + FullDescription = "

Sportliches Design für sportliche Touren: Die KANUKA POINT ist so gern in Bewegung wie du. Die Softshelljacke besteht aus superelastischem und sehr atmungsaktivem Material, das sich unterwegs jeder deiner Bewegungen anpasst. Deswegen nimmst du mit der KANUKA POINT jeden Pass mit Leichtigkeit.Und musst dir auch bei Kraxeleien zum Gipfel keine Gedanken um deine Jacke machen, denn ihr Material hält einiges aus.Auch bei Wind und leichten Schauern bleibst du gelassen.

", + Sku = "jack-1305851", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + Price = 83.90M, + OldPrice = 119.95M, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + IsShipEnabled = true, + DeliveryTime = firstDeliveryTime, + DisplayOrder = 5 + }; + + AddProductPicture(productJackWolfsKinKanukaPoint, "product_jack_wolfskin_kanuka_point_1.png", "jack-wolfskin-kanuka-point-1"); + + productJackWolfsKinKanukaPoint.ProductCategories.Add(new ProductCategory { Category = categoryJackets, DisplayOrder = 1 }); + + productJackWolfsKinKanukaPoint.ProductManufacturers.Add(new ProductManufacturer + { + Manufacturer = _ctx.Set().Where(c => c.Name == "Jack-Wolfskin").Single(), + DisplayOrder = 1, + }); + + #endregion product Jack Wolfskin KANUKA POINT + #endregion category jackets - var categoryGolf = this._ctx.Set().First(c => c.Alias == "Golf"); + + #region category golf #region product Titleist SM6 Tour Chrome - var productTitleistSM6TourChrome = new Product() + var productTitleistSM6TourChrome = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10188,7 +10405,7 @@ public IList Products() Published = true, MetaTitle = "Titleist SM6 Tour Chrome", Price = 164.95M, - OldPrice= 199.95M, + OldPrice = 199.95M, IsGiftCard = false, ManageInventoryMethod = ManageInventoryMethod.ManageStock, OrderMinimumQuantity = 1, @@ -10197,28 +10414,20 @@ 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 = categories["Golf"], DisplayOrder = 1 }); - productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), - DisplayOrder = 1, - }); + productTitleistSM6TourChrome.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Titleist"], DisplayOrder = 1 }); #endregion product Titleist SM6 Tour Chrome #region product Titleist Pro V1x - var productTitleistProV1x = new Product() + var productTitleistProV1x = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10240,28 +10449,20 @@ 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 = categories["Golf"], DisplayOrder = 1 }); - productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), - DisplayOrder = 1, - }); + productTitleistProV1x.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Titleist"], DisplayOrder = 1 }); #endregion product Titleist Pro V1x #region product Supreme Golfball - var productSupremeGolfball = new Product() + var productSupremeGolfball = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10283,34 +10484,21 @@ 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 }); + 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_1.jpg"), "image/png", GetSeName(productSupremeGolfball.Name)), - DisplayOrder = 1, - }); - - 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 = categories["Golf"], DisplayOrder = 1 }); - productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), - DisplayOrder = 1, - }); + productSupremeGolfball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Titleist"], DisplayOrder = 1 }); #endregion product Supreme Golfball #region product GBB Epic Sub Zero Driver - var productGBBEpicSubZeroDriver = new Product() + var productGBBEpicSubZeroDriver = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10332,22 +10520,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 = categories["Golf"], DisplayOrder = 1 }); - productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Titleist").Single(), - DisplayOrder = 1, - }); + productGBBEpicSubZeroDriver.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Titleist"], DisplayOrder = 1 }); #endregion product GBB Epic Sub Zero Driver @@ -10355,18 +10535,16 @@ public IList Products() #region category Soccer - var categorySoccer = this._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, Name = "Nike Strike Football", IsEsd = false, ShortDescription = "GREAT TOUCH. HIGH VISIBILITY.", - FullDescription = "

Enhance play everyday, with the Nike Strike Football.

Reinforced rubber retains its shape for confident and consistent control. A stand out Visual Power graphic in black, green and orange is best for ball tracking, despite dark or inclement conditions.

  • Visual Power graphic helps give a true read on flight trajectory.
  • Textured casing offers superior touch.
  • Reinforced rubber bladder supports air and shape retention.
  • 66% rubber/ 15% polyurethane/ 13% polyester/ 7% EVA.
", + FullDescription = "

Enhance play everyday, with the Nike Strike Football.

Reinforced rubber retains its shape for confident and consistent control. A stand out Visual Power graphic in black, green and orange is best for ball tracking, despite dark or inclement conditions.

  • Visual Power graphic helps give a true read on flight trajectory.
  • Textured casing offers superior touch.
  • Reinforced rubber bladder supports air and shape retention.
  • 66% rubber/ 15% polyurethane/ 13% polyester/ 7% EVA.
", Sku = "P-5004", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, @@ -10382,65 +10560,42 @@ 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 = categories["Soccer"], DisplayOrder = 1 }); - productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), - DisplayOrder = 1, - }); + productNikeStrikeFootball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Nike"], DisplayOrder = 1 }); - //attributes - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Manufacturer -> Nike - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder ==20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 20) }); - productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeStrikeFootball.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Material -> rubber - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 12).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 12) }); - #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,46 +10617,37 @@ 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 = categories["Soccer"], DisplayOrder = 1 }); - productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Nike").Single(), - DisplayOrder = 1, - }); + productNikeEvoPowerBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Nike"], DisplayOrder = 1 }); - //attributes - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Manufacturer -> Nike - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 20).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 20) }); - productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productNikeEvoPowerBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Material -> leather - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) }); #endregion Evopower 5.3 Trainer HS Ball #region product Torfabrik official game ball - var productTorfabrikOfficialGameBall = new Product() + var productTorfabrikOfficialGameBall = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10523,71 +10669,41 @@ 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 = categories["Soccer"], DisplayOrder = 1 }); - productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), - DisplayOrder = 1, - }); + productTorfabrikOfficialGameBall.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); - //attributes - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Manufacturer -> Adidas - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 19) }); - productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTorfabrikOfficialGameBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 3, // Material -> leather - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) }); - #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,88 +10725,47 @@ 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 }); + 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-white.png"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-white"), - DisplayOrder = 1, - }); + productAdidasTangoSalaBall.ProductCategories.Add(new ProductCategory { Category = categories["Soccer"], 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.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-red.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-red"), - DisplayOrder = 1, + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Manufacturer -> Adidas + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 19) }); - - productAdidasTangoSalaBall.ProductPictures.Add(new ProductPicture() + productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_adidas-tango-pasadena-ball-green.jpg"), "image/png", GetSeName(productAdidasTangoSalaBall.Name) + "-green"), - DisplayOrder = 1, + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Material -> leather + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) }); - 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, - }); - - 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.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), - DisplayOrder = 1, - }); - - //attributes - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Manufacturer -> Adidas - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 19).Single() - }); - productAdidasTangoSalaBall.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Material -> leather - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() - }); - - #endregion Adidas TANGO SALA BALL + #endregion Adidas TANGO SALA BALL #endregion category Soccer #region category Basketball - var categoryBasketball = this._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 +10788,25 @@ 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 }); - - productEvolutionHighSchoolGameBasketball.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_evolution-high-school-game-basketball.jpg"), "image/png", GetSeName(productEvolutionHighSchoolGameBasketball.Name)), - DisplayOrder = 1, - }); + AddProductPicture(productEvolutionHighSchoolGameBasketball, "product_evolution-high-school-game-basketball.jpg"); - productEvolutionHighSchoolGameBasketball.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), - DisplayOrder = 1, - }); + productEvolutionHighSchoolGameBasketball.ProductCategories.Add(new ProductCategory { Category = categories["Basketball"], 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.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); + 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,22 +10828,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 = categories["Basketball"], DisplayOrder = 1 }); - productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Adidas").Single(), - DisplayOrder = 1, - }); + productAllCourtBasketball.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Adidas"], DisplayOrder = 1 }); #endregion All Court Basketball @@ -10800,11 +10843,9 @@ public IList Products() #region category sunglasses - var categorySunglasses = this._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,40 +10867,22 @@ 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 }); + 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_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, - }); - - 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 = categories["Sunglasses"], DisplayOrder = 1 }); - productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), - DisplayOrder = 1, - }); + productRayBanTopBar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Ray-Ban"], DisplayOrder = 1 }); #endregion product Top bar #region product ORIGINAL WAYFARER AT COLLECTION - var productOriginalWayfarer = new Product() + var productOriginalWayfarer = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10881,58 +10904,25 @@ 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 = categories["Sunglasses"], DisplayOrder = 1 }); - productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Ray-Ban").Single(), - DisplayOrder = 1, - }); + productOriginalWayfarer.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Ray-Ban"], DisplayOrder = 1 }); #endregion product ORIGINAL WAYFARER AT COLLECTION #region product Radar EV Prizm Sports Sunglasses - var productRadarEVPrizmSportsSunglasses = new Product() + var productRadarEVPrizmSportsSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10954,28 +10944,20 @@ 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 = categories["Sunglasses"], DisplayOrder = 1 }); - productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Oakley").Single(), - DisplayOrder = 1, - }); + productRadarEVPrizmSportsSunglasses.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Oakley"], DisplayOrder = 1 }); #endregion product Radar EV Prizm Sports Sunglasses #region product Custom Flak Sunglasses - var productCustomFlakSunglasses = new Product() + var productCustomFlakSunglasses = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -10997,408 +10979,311 @@ 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 }); + 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 = categories["Sunglasses"], DisplayOrder = 1 }); + + productCustomFlakSunglasses.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Oakley"], DisplayOrder = 1 }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlakSunglasses.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlakSunglasses.jpg"), - DisplayOrder = 1, - }); + #endregion product Custom Flak Sunglasses - 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, - }); - 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() - { - 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() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_jadeiridium.jpg"), - DisplayOrder = 1, - }); + #endregion category sunglasses - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_positiverediridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_rubyiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_sapphireiridium.jpg"), - DisplayOrder = 1, - }); + #region category apple - 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() - { - 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() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_matteblack_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_matteblack_fireiridium.jpg"), - DisplayOrder = 1, - }); + #region product iphone plus - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_24kiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_24kiridium.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_clear.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_clear.jpg"), - DisplayOrder = 1, - }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + var productIphoneplus = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_fireiridium.jpg"), - DisplayOrder = 1, - }); + 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"); - 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() + productIphoneplus.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); + + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_positiverediridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_positiverediridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // offer type -> Permanent low price + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_rubyiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_rubyiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 64gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_sapphireiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 128gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + productIphoneplus.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_orangeflare_violetiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_orangeflare_violetiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) }); + #endregion product iphone plus - 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() + #region product Watch Series 2 + + var productWatchSeries2 = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_polishedwhite_fireiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_polishedwhite_fireiridium.jpg"), - DisplayOrder = 1, - }); + 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"); - 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, - }); + productWatchSeries2.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], 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, - }); + productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], 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() + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - 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"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // offer type -> offer of the day + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_gray.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_gray.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // storage capacity -> 32gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productCustomFlakSunglasses.ProductPictures.Add(new ProductPicture() + + productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_CustomFlak_redline_jadeiridium.jpg"), "image/png", GetSeName(productCustomFlakSunglasses.Name) + "product_CustomFlak_redline_jadeiridium.jpg"), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // operating system -> ios + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) }); - 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() + + #endregion product Watch Series 2 + + #region product Airpods + + var productAirpods = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Name = "iPhone Plus", + Name = "AirPods", 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.", + ShortDescription = "Wireless. Effortless. Magical. Just take them out and they’re ready to use with all your devices. Put them in your ears and they connect instantly. Speak into them and your voice sounds clear. Introducing AirPods. Simplicity and technology, together like never before. The result is completely magical.", FullDescription = "", - Sku = "P-2001", + Sku = "P-2003", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - MetaTitle = "iPhone Plus", - Price = 878M, + MetaTitle = "AirPods", + Price = 999M, IsGiftCard = false, ManageInventoryMethod = ManageInventoryMethod.ManageStock, OrderMinimumQuantity = 1, - OrderMaximumQuantity = 9, + OrderMaximumQuantity = 10000, StockQuantity = 10000, - DisplayStockAvailability = true, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - IsFreeShipping = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - - 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, - }); + 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"); - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_1.jpg"), "image/png", GetSeName(productIphoneplus.Name)), - DisplayOrder = 2, - }); + productAirpods.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); - 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, - }); + productAirpods.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 7 }); - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_black.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-black"), - DisplayOrder = 2, - }); + #endregion product Airpods - productIphoneplus.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_rose.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-rose"), - DisplayOrder = 2, - }); + #region product Ultimate Apple Pro Hipster Bundle - productIphoneplus.ProductPictures.Add(new ProductPicture() + var productAppleProHipsterBundle = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphone-plus_gold.jpg"), "image/png", GetSeName(productIphoneplus.Name) + "-gold"), - DisplayOrder = 2, - }); + ProductType = ProductType.BundledProduct, + VisibleIndividually = true, + Name = "Ultimate Apple Pro Hipster Bundle", + IsEsd = false, + ShortDescription = "Save with this set 5%!", + FullDescription = "As an Apple fan and hipster, it is your basic need to always have the latest Apple products. So you do not have to spend four times a year in front of the Apple Store, simply subscribe to the Ultimate Apple Pro Hipster Set in the year subscription!", + Sku = "P-2005-Bundle", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + ShowOnHomePage = true, + MetaTitle = "Ultimate Apple Pro Hipster Bundle", + Price = 2371M, + IsGiftCard = false, + ManageInventoryMethod = ManageInventoryMethod.ManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id, + BundleTitleText = "Bundle includes", + BundlePerItemPricing = true, + BundlePerItemShoppingCart = true + }; - //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() - }); + 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"); - 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() - }); + productAppleProHipsterBundle.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); - #endregion product iphone plus + productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 1 }); - #region product Watch Series 2 + #endregion product Ultimate Apple Pro Hipster Bundle + + #region product 9,7 iPad - var productWatchSeries2 = new Product() + var product97ipad = new Product { ProductType = ProductType.SimpleProduct, - VisibleIndividually = false, - Name = "Watch Series 2", + VisibleIndividually = true, + Name = "9,7' iPad", 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", + ShortDescription = "Flat-out fun. Learn, play, surf, create. iPad gives you the incredible display, performance, and apps to do what you love to do. Anywhere. Easily. Magically.", + FullDescription = "
  • 9,7' Retina Display mit True Tone und
  • A9X Chip der dritten Generation mit 64-Bit Desktoparchitektur
  • Touch ID Fingerabdrucksensor
  • 12 Megapixel iSight Kamera mit 4K Video
  • 5 Megapixel FaceTime HD Kamera
  • 802.11ac WLAN mit MIMO
  • Bis zu 10 Stunden Batterielaufzeit***
  • 4-Lautsprecher-Audio
", + Sku = "P-2004", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - MetaTitle = "Watch Series 2", - Price = 299M, - OldPrice = 399M, + ShowOnHomePage = true, + MetaTitle = "9,7' iPad", + Price = 319.00M, + OldPrice = 349.00M, + SpecialPrice = 299.00M, + SpecialPriceStartDateTimeUtc = new DateTime(2017, 5, 1, 0, 0, 0), + SpecialPriceEndDateTimeUtc = specialPriceEndDate, IsGiftCard = false, ManageInventoryMethod = ManageInventoryMethod.ManageStock, OrderMinimumQuantity = 1, @@ -11407,1496 +11292,695 @@ public IList Products() NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DeliveryTimeId = thirdDeliveryTime.Id }; - productWatchSeries2.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + 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"); - productWatchSeries2.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_1.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 1, - }); + product97ipad.ProductCategories.Add(new ProductCategory { Category = categories["Apple"], DisplayOrder = 1 }); - productWatchSeries2.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productWatchSeries2.Name)), - DisplayOrder = 2, - }); + product97ipad.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Apple"], DisplayOrder = 1 }); - productWatchSeries2.ProductManufacturers.Add(new ProductManufacturer() + product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), + AllowFiltering = true, + ShowOnProductPage = true, DisplayOrder = 1, + // offer type -> promotion + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - //attributes - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.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() + // storage capacity -> 64gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.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() + // storage capacity -> 128gb + SpecificationAttributeOption = specAttributes[27].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - - productWatchSeries2.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + product97ipad.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() + SpecificationAttributeOption = specAttributes[5].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) }); + #endregion product 9,7 iPad - #endregion product Watch Series 2 + #endregion category apple - #region product Airpods + #region category Gift Cards + + #region product10GiftCard - var productAirpods = new Product() + var product10GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Name = "AirPods", - IsEsd = false, - ShortDescription = "Wireless. Effortless. Magical. Just take them out and they’re ready to use with all your devices. Put them in your ears and they connect instantly. Speak into them and your voice sounds clear. Introducing AirPods. Simplicity and technology, together like never before. The result is completely magical.", - FullDescription = "", - Sku = "P-2003", + Name = "$10 Virtual Gift Card", + IsEsd = true, + ShortDescription = "$10 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", + Sku = "P-1000", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - MetaTitle = "AirPods", - Price = 999M, - IsGiftCard = false, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, + MetaTitle = "$10 Virtual Gift Card", + Price = 10M, + IsGiftCard = true, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10000, StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DisplayOrder = 1 }; - productAirpods.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); - - 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.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - DisplayOrder = 7, - }); + AddProductPicture(product10GiftCard, "product_gift_card_10.png"); + product10GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); - #endregion product Airpods + #endregion product10GiftCard - #region product Ultimate Apple Pro Hipster Bundle + #region product25GiftCard - var productAppleProHipsterBundle = new Product() + var product25GiftCard = new Product { - ProductType = ProductType.BundledProduct, + ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Name = "Ultimate Apple Pro Hipster Bundle", - IsEsd = false, - ShortDescription = "Save with this set 5%!", - FullDescription = "As an Apple fan and hipster, it is your basic need to always have the latest Apple products. So you do not have to spend four times a year in front of the Apple Store, simply subscribe to the Ultimate Apple Pro Hipster Set in the year subscription!", - Sku = "P-2005-Bundle", + Name = "$25 Virtual Gift Card", + IsEsd = true, + ShortDescription = "$25 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", + Sku = "P-1001", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - ShowOnHomePage = true, - MetaTitle = "Ultimate Apple Pro Hipster Bundle", - Price = 2371M, - IsGiftCard = false, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, + MetaTitle = "$25 Virtual Gift Card", + Price = 25M, + IsGiftCard = true, + GiftCardType = GiftCardType.Virtual, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10000, StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single(), - BundleTitleText = "Bundle includes", - BundlePerItemPricing = true, - BundlePerItemShoppingCart = true + DisplayOrder = 2 }; - - 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, - }); + AddProductPicture(product25GiftCard, "product_gift_card_25.png"); + product25GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "products_airpods_white.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + #endregion product25GiftCard - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_watchseries2_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + #region product50GiftCard - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() + var product50GiftCard = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_iphoneplus_2.jpg"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "$50 Virtual Gift Card", + IsEsd = true, + ShortDescription = "$50 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", + Sku = "P-1002", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "$50 Virtual Gift Card", + Price = 50M, + IsGiftCard = true, + GiftCardType = GiftCardType.Virtual, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + DisplayOrder = 3 + }; - productAppleProHipsterBundle.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_apple.png"), "image/png", GetSeName(productAppleProHipsterBundle.Name)), - DisplayOrder = 2, - }); + AddProductPicture(product50GiftCard, "product_gift_card_50.png"); + product50GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], DisplayOrder = 1 }); - productAppleProHipsterBundle.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - DisplayOrder = 1, - }); + #endregion product50GiftCard - #endregion product Ultimate Apple Pro Hipster Bundle - - #region product 9,7 iPad + #region product100GiftCard - var product97ipad = new Product() + var product100GiftCard = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Name = "9,7' iPad", - IsEsd = false, - ShortDescription = "Flat-out fun. Learn, play, surf, create. iPad gives you the incredible display, performance, and apps to do what you love to do. Anywhere. Easily. Magically.", - FullDescription = "
  • 9,7' Retina Display mit True Tone und
  • A9X Chip der dritten Generation mit 64-Bit Desktoparchitektur
  • Touch ID Fingerabdrucksensor
  • 12 Megapixel iSight Kamera mit 4K Video
  • 5 Megapixel FaceTime HD Kamera
  • 802.11ac WLAN mit MIMO
  • Bis zu 10 Stunden Batterielaufzeit***
  • 4-Lautsprecher-Audio
", - Sku = "P-2004", + Name = "$100 Virtual Gift Card", + IsEsd = true, + ShortDescription = "$100 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", + FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", + Sku = "P-10033", ProductTemplateId = productTemplate.Id, AllowCustomerReviews = true, Published = true, - ShowOnHomePage = true, - MetaTitle = "9,7' iPad", - Price = 319.00M, - OldPrice = 349.00M, - SpecialPrice = 299.00M, - SpecialPriceStartDateTimeUtc = new DateTime(2017, 5, 1, 0, 0, 0), - SpecialPriceEndDateTimeUtc = specialPriceEndDate, - IsGiftCard = false, - ManageInventoryMethod = ManageInventoryMethod.ManageStock, + MetaTitle = "$100 Virtual Gift Card", + Price = 100M, + IsGiftCard = true, + GiftCardType = GiftCardType.Virtual, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10000, StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() + DisplayOrder = 4, }; - product97ipad.ProductCategories.Add(new ProductCategory() { Category = categoryApple, DisplayOrder = 1 }); + AddProductPicture(product100GiftCard, "product_gift_card_100.png"); + product100GiftCard.ProductCategories.Add(new ProductCategory { Category = categories["Gift Cards"], 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, - }); + #endregion product100GiftCard - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_ipad_2.jpg"), "image/png", GetSeName(product97ipad.Name)), - DisplayOrder = 2, - }); + #endregion category Gift Cards - 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, - }); + #region category books - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-lightblue.jpg"), "image/png", GetSeName(product97ipad.Name) + "-lightblue"), - DisplayOrder = 2, - }); + #region productBooksUberMan - product97ipad.ProductPictures.Add(new ProductPicture() + var productBooksUberMan = new Product { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-purple.jpg"), "image/png", GetSeName(product97ipad.Name) + "-purple"), - DisplayOrder = 2, - }); + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Überman: The novel", + ShortDescription = "(Hardcover)", + FullDescription = "

From idiots to riches - and back ... Ever since it with my Greek financial advisors were no more delicious cookies to meetings, I should have known something. Was the last cookie it when I bought a Romanian forest funds and leveraged discount certificates on lean hogs - which is sort of a more stringent bet that the price of lean hogs will remain stable, and that's nothing special because it is also available for cattle and cotton and fat pig. Again and again and I joked Kosmas Nikiforos Sarantakos. About all the part-time seer who tremblingly put for fear the euro crisis gold coins under the salami slices of their frozen pizzas And then came the day that revealed to me in almost Sarantakos fraudulent casualness that my plan had not worked out really. 'Why all of a sudden> my plan', 'I heard myself asking yet, but it was in the garage I realized what that really meant minus 211.2 percent in my portfolio report: personal bankruptcy, gutter and Drug Addiction with subsequent loss of the incisors . Not even the study of my friend, I would still be able to finance. The only way out was to me as quickly as secretly again to draw from this unspeakable Greek shit - I had to be Überman! By far the bekloppteste story about 'idiot' Simon Peter! »Tommy Jaud – Deutschlands witzigste Seite.« Alex Dengler, Bild am Sonntag

", + Sku = "P-1003", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Überman: The novel", + Price = 16.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true + }; - product97ipad.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-mint.jpg"), "image/png", GetSeName(product97ipad.Name) + "-mint"), - DisplayOrder = 2, - }); + AddProductPicture(productBooksUberMan, "0000932_uberman-der-roman.jpeg"); + productBooksUberMan.ProductCategories.Add(new ProductCategory { Category = categories["SPIEGEL-Bestseller"], DisplayOrder = 1 }); - product97ipad.ProductPictures.Add(new ProductPicture() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-rose.jpg"), "image/png", GetSeName(product97ipad.Name) + "-rose"), - DisplayOrder = 2, + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - - product97ipad.ProductPictures.Add(new ProductPicture() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-spacegray.jpg"), "image/png", GetSeName(product97ipad.Name) + "-spacegray"), - DisplayOrder = 2, + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) }); - - product97ipad.ProductPictures.Add(new ProductPicture() + productBooksUberMan.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_97-ipad-gold.jpg"), "image/png", GetSeName(product97ipad.Name) + "-gold"), - DisplayOrder = 2, + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - 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 + #endregion productBooksUberMan - product97ipad.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - DisplayOrder = 1, - }); + #region productBooksGefangeneDesHimmels - #region attributes - //attributes - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + var productBooksGefangeneDesHimmels = new Product { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // offer type -> promotion - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() - }); - - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "The Prisoner of Heaven: A Novel", + ShortDescription = "(Hardcover)", + FullDescription = "

By Shadow of the Wind and The Angel's Game, the new large-Barcelona novel by Carlos Ruiz Zafón. - Barcelona, Christmas 1957th The bookseller Daniel Sempere and his friend Fermín be drawn again into a great adventure. In the continuation of his international success with Carlos Ruiz Zafón takes the reader on a fascinating journey into his Barcelona. Creepy and fascinating, with incredible suction power and humor, the novel, the story of Fermin, who 'rose from the dead, and the key to the future is.' Fermin's life story linking the threads of The Shadow of the Wind with those from The Angel's Game. A masterful puzzle that keeps the reader around the world in thrall.

Product Hardcover: 416 pages Publisher: S. Fischer Verlag; 1 edition (October 25, 2012) Language: German ISBN-10: 3,100,954,025 ISBN-13: 978-3100954022 Original title: El prisionero del cielo Size and / or weight: 21.4 x 13.6 cm x 4.4

", + ProductTemplateId = productTemplate.Id, + Sku = "P-1004", + AllowCustomerReviews = true, + Published = true, + MetaTitle = "The Prisoner of Heaven: A Novel", + Price = 22.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; + + AddProductPicture(productBooksGefangeneDesHimmels, "0000935_der-gefangene-des-himmels-roman_300.jpeg"); + productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory { Category = categories["SPIEGEL-Bestseller"], DisplayOrder = 1 }); + + productBooksGefangeneDesHimmels.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() + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + + productBooksGefangeneDesHimmels.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() + DisplayOrder = 3, + // Category -> bound + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) }); - product97ipad.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksGefangeneDesHimmels.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() + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - #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"); - - #region product10GiftCard - - var product10GiftCard = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$10 Virtual Gift Card", - IsEsd = true, - ShortDescription = "$10 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", - Sku = "P-1000", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "$10 Virtual Gift Card", - Price = 10M, - IsGiftCard = true, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - 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, - }); - - #endregion product10GiftCard - - #region product25GiftCard - - var product25GiftCard = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$25 Virtual Gift Card", - IsEsd = true, - ShortDescription = "$25 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", - Sku = "P-1001", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "$25 Virtual Gift Card", - Price = 25M, - IsGiftCard = true, - GiftCardType = GiftCardType.Virtual, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - 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, - }); - - #endregion product25GiftCard - - #region product50GiftCard - - var product50GiftCard = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "$50 Virtual Gift Card", - IsEsd = true, - ShortDescription = "$50 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", - Sku = "P-1002", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "$50 Virtual Gift Card", - Price = 50M, - IsGiftCard = true, - GiftCardType = GiftCardType.Virtual, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - 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, - }); - - #endregion product50GiftCard + #endregion productBooksGefangeneDesHimmels - #region product100GiftCard + #region productBooksBestGrillingRecipes - var product100GiftCard = new Product() + var productBooksBestGrillingRecipes = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, - Name = "$100 Virtual Gift Card", - IsEsd = true, - ShortDescription = "$100 Gift Card. Gift Cards must be redeemed through our site Web site toward the purchase of eligible products.", - FullDescription = "

Gift Cards must be redeemed through our site Web site toward the purchase of eligible products. Purchases are deducted from the GiftCard balance. Any unused balance will be placed in the recipient's GiftCard account when redeemed. If an order exceeds the amount of the GiftCard, the balance must be paid with a credit card or other available payment method.

", - Sku = "P-10033", + Name = "Best Grilling Recipes", + ShortDescription = "More Than 100 Regional Favorites Tested and Perfected for the Outdoor Cook (Hardcover)", + FullDescription = "

Take a winding cross-country trip and you'll discover barbecue shacks with offerings like tender-smoky Baltimore pit beef and saucy St. Louis pork steaks. To bring you the best of these hidden gems, along with all the classics, the editors of Cook's Country magazine scoured the country, then tested and perfected their favorites. HEre traditions large and small are brought into the backyard, from Hawaii's rotisserie favorite, the golden-hued Huli Huli Chicken, to fall-off-the-bone Chicago Barbecued Ribs. In Kansas City, they're all about the sauce, and for our saucy Kansas City Sticky Ribs, we found a surprise ingredient-root beer. We also tackle all the best sides.

Not sure where or how to start? This cookbook kicks off with an easy-to-follow primer that will get newcomers all fired up. Whether you want to entertain a crowd or just want to learn to make perfect burgers, Best Grilling Recipes shows you the way.

", ProductTemplateId = productTemplate.Id, + Sku = "P-1005", AllowCustomerReviews = true, Published = true, - MetaTitle = "$100 Virtual Gift Card", - Price = 100M, - IsGiftCard = true, - GiftCardType = GiftCardType.Virtual, + MetaTitle = "Best Grilling Recipes", + Price = 27.00M, ManageInventoryMethod = ManageInventoryMethod.DontManageStock, OrderMinimumQuantity = 1, OrderMaximumQuantity = 10000, StockQuantity = 10000, NotifyAdminForQuantityBelow = 1, AllowBackInStockSubscriptions = false, - DisplayOrder = 4, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id }; - product100GiftCard.ProductCategories.Add(new ProductCategory() { Category = categoryGiftCards, DisplayOrder = 1 }); + AddProductPicture(productBooksBestGrillingRecipes, "product_bestgrillingrecipes.jpg"); + productBooksBestGrillingRecipes.ProductCategories.Add(new ProductCategory { Category = categories["Cook and enjoy"], DisplayOrder = 1 }); - product100GiftCard.ProductPictures.Add(new ProductPicture() + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_gift_card_100.png"), "image/png", GetSeName(product100GiftCard.Name)), - //DisplayOrder = 4, - }); - - #endregion product100GiftCard - - #endregion category Gift Cards - - #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"); - - #region productBooksUberMan - - var productBooksUberMan = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Überman: The novel", - ShortDescription = "(Hardcover)", - FullDescription = "

From idiots to riches - and back ... Ever since it with my Greek financial advisors were no more delicious cookies to meetings, I should have known something. Was the last cookie it when I bought a Romanian forest funds and leveraged discount certificates on lean hogs - which is sort of a more stringent bet that the price of lean hogs will remain stable, and that's nothing special because it is also available for cattle and cotton and fat pig. Again and again and I joked Kosmas Nikiforos Sarantakos. About all the part-time seer who tremblingly put for fear the euro crisis gold coins under the salami slices of their frozen pizzas And then came the day that revealed to me in almost Sarantakos fraudulent casualness that my plan had not worked out really. 'Why all of a sudden> my plan', 'I heard myself asking yet, but it was in the garage I realized what that really meant minus 211.2 percent in my portfolio report: personal bankruptcy, gutter and Drug Addiction with subsequent loss of the incisors . Not even the study of my friend, I would still be able to finance. The only way out was to me as quickly as secretly again to draw from this unspeakable Greek shit - I had to be Überman! By far the bekloppteste story about 'idiot' Simon Peter! »Tommy Jaud – Deutschlands witzigste Seite.« Alex Dengler, Bild am Sonntag

", - Sku = "P-1003", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Überman: The novel", - Price = 16.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - 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, - }); - - //attributes - 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() - { - 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() - { - 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() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "The Prisoner of Heaven: A Novel", - ShortDescription = "(Hardcover)", - FullDescription = "

By Shadow of the Wind and The Angel's Game, the new large-Barcelona novel by Carlos Ruiz Zafón. - Barcelona, Christmas 1957th The bookseller Daniel Sempere and his friend Fermín be drawn again into a great adventure. In the continuation of his international success with Carlos Ruiz Zafón takes the reader on a fascinating journey into his Barcelona. Creepy and fascinating, with incredible suction power and humor, the novel, the story of Fermin, who 'rose from the dead, and the key to the future is.' Fermin's life story linking the threads of The Shadow of the Wind with those from The Angel's Game. A masterful puzzle that keeps the reader around the world in thrall.

Product Hardcover: 416 pages Publisher: S. Fischer Verlag; 1 edition (October 25, 2012) Language: German ISBN-10: 3,100,954,025 ISBN-13: 978-3100954022 Original title: El prisionero del cielo Size and / or weight: 21.4 x 13.6 cm x 4.4

", - ProductTemplateId = productTemplate.Id, - Sku = "P-1004", - AllowCustomerReviews = true, - Published = true, - MetaTitle = "The Prisoner of Heaven: A Novel", - Price = 22.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksGefangeneDesHimmels.ProductCategories.Add(new ProductCategory() { Category = categorySpiegelBestseller, DisplayOrder = 1 }); - - //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, - }); - - //attributes - productBooksGefangeneDesHimmels.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() - }); - - productBooksGefangeneDesHimmels.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() - }); - productBooksGefangeneDesHimmels.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 productBooksGefangeneDesHimmels - - #region productBooksBestGrillingRecipes - - var productBooksBestGrillingRecipes = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Best Grilling Recipes", - ShortDescription = "More Than 100 Regional Favorites Tested and Perfected for the Outdoor Cook (Hardcover)", - FullDescription = "

Take a winding cross-country trip and you'll discover barbecue shacks with offerings like tender-smoky Baltimore pit beef and saucy St. Louis pork steaks. To bring you the best of these hidden gems, along with all the classics, the editors of Cook's Country magazine scoured the country, then tested and perfected their favorites. HEre traditions large and small are brought into the backyard, from Hawaii's rotisserie favorite, the golden-hued Huli Huli Chicken, to fall-off-the-bone Chicago Barbecued Ribs. In Kansas City, they're all about the sauce, and for our saucy Kansas City Sticky Ribs, we found a surprise ingredient-root beer. We also tackle all the best sides.

Not sure where or how to start? This cookbook kicks off with an easy-to-follow primer that will get newcomers all fired up. Whether you want to entertain a crowd or just want to learn to make perfect burgers, Best Grilling Recipes shows you the way.

", - ProductTemplateId = productTemplate.Id, - Sku = "P-1005", - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Best Grilling Recipes", - Price = 27.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() - }; - - 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() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Edition -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); - productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> cook & bake - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() - }); - productBooksBestGrillingRecipes.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 == 2).Single() - }); + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cook & bake + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 8) + }); + productBooksBestGrillingRecipes.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); - #endregion productBooksBestGrillingRecipes + #endregion productBooksBestGrillingRecipes - #region productBooksCookingForTwo + #region productBooksCookingForTwo - var productBooksCookingForTwo = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Cooking for Two", - ShortDescription = "More Than 200 Foolproof Recipes for Weeknights and Special Occasions (Hardcover)", - FullDescription = "

In Cooking for Two, the test kitchen's goal was to take traditional recipes and cut them down to size to serve just twowith tailored cooking techniques and smart shopping tips that will cut down on wasted food and wasted money. Great lasagna starts to lose its luster when you're eating the leftovers for the fourth day in a row. While it may seem obvious that a recipe for four can simply be halved to work, our testing has proved that this is not always the case; cooking with smaller amounts of ingredients often requires different preparation techniques, cooking time, temperature, and the proportion of ingredients. This was especially true as we worked on scaled-down desserts; baking is an unforgiving science in which any changes in recipe amounts often called for changes in baking times and temperatures.

Hardcover: 352 pages
Publisher: America's Test Kitchen (May 2009)
Language: English
ISBN-10: 1933615435
ISBN-13: 978-1933615431

", - ProductTemplateId = productTemplate.Id, + var productBooksCookingForTwo = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Cooking for Two", + ShortDescription = "More Than 200 Foolproof Recipes for Weeknights and Special Occasions (Hardcover)", + FullDescription = "

In Cooking for Two, the test kitchen's goal was to take traditional recipes and cut them down to size to serve just twowith tailored cooking techniques and smart shopping tips that will cut down on wasted food and wasted money. Great lasagna starts to lose its luster when you're eating the leftovers for the fourth day in a row. While it may seem obvious that a recipe for four can simply be halved to work, our testing has proved that this is not always the case; cooking with smaller amounts of ingredients often requires different preparation techniques, cooking time, temperature, and the proportion of ingredients. This was especially true as we worked on scaled-down desserts; baking is an unforgiving science in which any changes in recipe amounts often called for changes in baking times and temperatures.

Hardcover: 352 pages
Publisher: America's Test Kitchen (May 2009)
Language: English
ISBN-10: 1933615435
ISBN-13: 978-1933615431

", + ProductTemplateId = productTemplate.Id, Sku = "P-1006", - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Cooking for Two", - Price = 27.00M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 1).Single() - }; - - productBooksCookingForTwo.ProductCategories.Add(new ProductCategory() { Category = categoryCookAndEnjoy, DisplayOrder = 1 }); + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Cooking for Two", + Price = 27.00M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + 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 = categories["Cook and enjoy"], DisplayOrder = 1 }); - //attributes - productBooksCookingForTwo.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() - }); - productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> cook & bake - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() - }); - productBooksCookingForTwo.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 == 2).Single() - }); + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cook & bake + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 8) + }); + productBooksCookingForTwo.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) + }); - #endregion productBooksCookingForTwo + #endregion productBooksCookingForTwo - #region productBooksAutosDerSuperlative + #region productBooksAutosDerSuperlative - var productBooksAutosDerSuperlative = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Car of superlatives: the strongest, the first, the most beautiful, the fastest", - ShortDescription = "Hardcover", - FullDescription = "

For some, the car is only a useful means of transportation. For everyone else, there are 'cars - The Ultimate Guide' of art-connoisseur Michael Doerflinger. With authentic images, all important data and a lot of information can be presented to the fastest, most innovative, the strongest, the most unusual and the most successful examples of automotive history. A comprehensive manual for the specific reference and extensive browsing.

", + var productBooksAutosDerSuperlative = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Car of superlatives: the strongest, the first, the most beautiful, the fastest", + ShortDescription = "Hardcover", + FullDescription = "

For some, the car is only a useful means of transportation. For everyone else, there are 'cars - The Ultimate Guide' of art-connoisseur Michael Doerflinger. With authentic images, all important data and a lot of information can be presented to the fastest, most innovative, the strongest, the most unusual and the most successful examples of automotive history. A comprehensive manual for the specific reference and extensive browsing.

", Sku = "P-1007", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Car of superlatives", - Price = 14.95M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 2).Single() - }; - - 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() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Edition -> bound - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 13).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() - }); - productBooksAutosDerSuperlative.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 == 6).Single() - }); - productBooksAutosDerSuperlative.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 productBooksAutosDerSuperlative - - #region productBooksBildatlasMotorraeder - - var productBooksBildatlasMotorraeder = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Picture Atlas Motorcycles: With more than 350 brilliant images", - ShortDescription = "Hardcover", - FullDescription = "

Motorcycles are like no other means of transportation for the great dream of freedom and adventure. This richly illustrated atlas image portrayed in brilliant color photographs and informative text, the most famous bikes of the world's motorcycle history. From the primitive steam engine under the saddle of the late 19th Century up to the hugely powerful, equipped with the latest electronics and computer technology superbikes of today he is an impressive picture of the development and fabrication of noble and fast-paced motorcycles. The myth of the motorcycle is just as much investigated as a motorcycle as a modern lifestyle product of our time. Country-specific, company-historical background information and interesting stories and History about the people who preceded drove one of the seminal inventions of recent centuries and evolved, make this comprehensive illustrated book an incomparable reference for any motorcycle enthusiast and technology enthusiasts.

• Extensive history of the legendary models of all major motorcycle manufacturers worldwide
• With more than 350 brilliant color photographs and fascinating background information relating
• With informative drawings, stunning detail shots and explanatory info-boxes

content • 1817 1913: The beginning of a success story
• 1914 1945: mass mobility
• 1946 1990: Battle for the World Market
• In 1991: The modern motorcycle
• motorcycle cult object: From Transportation to Lifestyle

", - Sku = "P-1008", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Picture Atlas Motorcycles", - Price = 14.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); - - //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, - }); - - //attributes - productBooksBildatlasMotorraeder.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() - }); - productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> non-fiction - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - }); - productBooksBildatlasMotorraeder.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 productBooksBildatlasMotorraeder - - #region productBooksAutoBuch - - var productBooksAutoBuch = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "The Car Book. The great history with over 1200 models", - ShortDescription = "Hardcover", - FullDescription = "

Makes, models, milestones
The car - for some, a utensil for other expression of lifestyle, cult object and passion. Few inventions have changed their lives as well as the good of the automobile 125 years ago - one more reason for this extensive chronicle. The car-book brings the history of the automobile to life. It presents more than 1200 important models - Karl Benz 'Motorwagen about legendary cult car to advanced hybrid vehicles. It explains the milestones in engine technology and portrays the big brands and their designers. Characteristics from small cars to limousines and send racing each era invite you to browse and discover. The most comprehensive and bestbebildert illustrated book on the market - it would be any car lover!

Hardcover: 360 pages
Publisher: Dorling Kindersley Publishing (September 27, 2012)
Language: German
ISBN-10: 3,831,022,062
ISBN-13: 978-3831022069
Size and / or weight: 30.6 x 25.8 x 2.8 cm

", - Sku = "P-1009", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "The Car Book", - Price = 29.95M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksAutoBuch.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); - - //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, - }); - - //attributes - productBooksAutoBuch.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() - }); - productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 3, - // Category -> non-fiction - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 14).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 9).Single() - }); - productBooksAutoBuch.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 productBooksAutoBuch - - #region productBooksFastCars - - var productBooksFastCars = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Fast Cars, Image Calendar 2013", - ShortDescription = "spiral bound", - FullDescription = "

Large Size: 48.5 x 34 cm.
This impressive picture calendar with silver ring binding thrilled with impressive photographs of exclusive sports cars. Who understands cars not only as a pure commercial vehicles, will find the most sought-after status symbols at all: fast cars are effectively set to the razor sharp and vivid photos in scene and convey freedom, speed, strength and the highest technical perfection. Starting with the 450-horsepower Maserati GranTurismo MC Stradale on the stylish, luxurious Aston Martin Virage Volante accompany up to the produced only in small numbers Mosler Photon MT900S the fast racer with style and elegance through the months.

Besides the calendar draws another picture to look at interesting details. There are the essential information on any sports car in the English language. After this year, the high-quality photos are framed an eye-catcher on the wall of every lover of fast cars. Even as a gift this beautiful years companion is wonderfully suited. 12 calendar pages, neutral and discreet held calendar. Printed on paper from sustainable forests. For lovers of luxury vintage cars also available in ALPHA EDITION: the large format image Classic Cars Calendar 2013: ISBN 9,783,840,733,376th

Spiral-bound: 14 pages
Publisher: Alpha Edition (June 1, 2012)
Language: German
ISBN-10: 3,840,733,383
ISBN-13: 978-3840733383
Size and / or weight: 48.8 x 34.2 x 0.6 cm

", - Sku = "P-1010", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Fast Cars", - Price = 16.95M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = _ctx.Set().Where(sa => sa.DisplayOrder == 0).Single() - }; - - productBooksFastCars.ProductCategories.Add(new ProductCategory() { Category = categoryBooks, DisplayOrder = 1 }); - - //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, - }); - - //attributes - productBooksFastCars.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() - }); - productBooksFastCars.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 == 6).Single() - }); - productBooksFastCars.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 productBooksFastCars - - #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 }); + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Car of superlatives", + Price = 14.95M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; - //#region manufacturer + AddProductPicture(productBooksAutosDerSuperlative, "0000944_autos-der-superlative-die-starksten-die-ersten-die-schonsten-die-schnellsten.jpeg"); + productBooksAutosDerSuperlative.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); - ////manufacturer - //productComputerAcerAspireOne.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Acer").Single(), - // DisplayOrder = 1, - //}); + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cars + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + productBooksAutosDerSuperlative.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); - //#endregion manufacturer + #endregion productBooksAutosDerSuperlative - //#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 productBooksBildatlasMotorraeder - //#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, - //}); + var productBooksBildatlasMotorraeder = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Picture Atlas Motorcycles: With more than 350 brilliant images", + ShortDescription = "Hardcover", + FullDescription = "

Motorcycles are like no other means of transportation for the great dream of freedom and adventure. This richly illustrated atlas image portrayed in brilliant color photographs and informative text, the most famous bikes of the world's motorcycle history. From the primitive steam engine under the saddle of the late 19th Century up to the hugely powerful, equipped with the latest electronics and computer technology superbikes of today he is an impressive picture of the development and fabrication of noble and fast-paced motorcycles. The myth of the motorcycle is just as much investigated as a motorcycle as a modern lifestyle product of our time. Country-specific, company-historical background information and interesting stories and History about the people who preceded drove one of the seminal inventions of recent centuries and evolved, make this comprehensive illustrated book an incomparable reference for any motorcycle enthusiast and technology enthusiasts.

• Extensive history of the legendary models of all major motorcycle manufacturers worldwide
• With more than 350 brilliant color photographs and fascinating background information relating
• With informative drawings, stunning detail shots and explanatory info-boxes

content • 1817 1913: The beginning of a success story
• 1914 1945: mass mobility
• 1946 1990: Battle for the World Market
• In 1991: The modern motorcycle
• motorcycle cult object: From Transportation to Lifestyle

", + Sku = "P-1008", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Picture Atlas Motorcycles", + Price = 14.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; - //#endregion tierPrieces + AddProductPicture(productBooksBildatlasMotorraeder, "0000942_bildatlas-motorrader-mit-mehr-als-350-brillanten-abbildungen.jpeg"); + productBooksBildatlasMotorraeder.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); - //#endregion productComputerAcerAspireOne + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> non-fiction + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + productBooksBildatlasMotorraeder.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); - #endregion computer + #endregion productBooksBildatlasMotorraeder - #region Smartphones + #region productBooksAutoBuch - // var categoryCellPhones = this._ctx.Set().First(c => c.Alias == "Cell phones"); + var productBooksAutoBuch = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "The Car Book. The great history with over 1200 models", + ShortDescription = "Hardcover", + FullDescription = "

Makes, models, milestones
The car - for some, a utensil for other expression of lifestyle, cult object and passion. Few inventions have changed their lives as well as the good of the automobile 125 years ago - one more reason for this extensive chronicle. The car-book brings the history of the automobile to life. It presents more than 1200 important models - Karl Benz 'Motorwagen about legendary cult car to advanced hybrid vehicles. It explains the milestones in engine technology and portrays the big brands and their designers. Characteristics from small cars to limousines and send racing each era invite you to browse and discover. The most comprehensive and bestbebildert illustrated book on the market - it would be any car lover!

Hardcover: 360 pages
Publisher: Dorling Kindersley Publishing (September 27, 2012)
Language: German
ISBN-10: 3,831,022,062
ISBN-13: 978-3831022069
Size and / or weight: 30.6 x 25.8 x 2.8 cm

", + Sku = "P-1009", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "The Car Book", + Price = 29.95M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; - //#region productSmartPhonesAppleIphone + AddProductPicture(productBooksAutoBuch, "0000947_das-auto-buch-die-grose-chronik-mit-uber-1200-modellen_300.jpeg"); + productBooksAutoBuch.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); - //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() - //}; + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> non-fiction + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + productBooksAutoBuch.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); - // productSmartPhonesAppleIphone.ProductCategories.Add(new ProductCategory() { Category = categoryCellPhones, DisplayOrder = 1 }); + #endregion productBooksAutoBuch - //#region pictures + #region productBooksFastCars - ////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, - //}); + var productBooksFastCars = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Fast Cars, Image Calendar 2013", + ShortDescription = "spiral bound", + FullDescription = "

Large Size: 48.5 x 34 cm.
This impressive picture calendar with silver ring binding thrilled with impressive photographs of exclusive sports cars. Who understands cars not only as a pure commercial vehicles, will find the most sought-after status symbols at all: fast cars are effectively set to the razor sharp and vivid photos in scene and convey freedom, speed, strength and the highest technical perfection. Starting with the 450-horsepower Maserati GranTurismo MC Stradale on the stylish, luxurious Aston Martin Virage Volante accompany up to the produced only in small numbers Mosler Photon MT900S the fast racer with style and elegance through the months.

Besides the calendar draws another picture to look at interesting details. There are the essential information on any sports car in the English language. After this year, the high-quality photos are framed an eye-catcher on the wall of every lover of fast cars. Even as a gift this beautiful years companion is wonderfully suited. 12 calendar pages, neutral and discreet held calendar. Printed on paper from sustainable forests. For lovers of luxury vintage cars also available in ALPHA EDITION: the large format image Classic Cars Calendar 2013: ISBN 9,783,840,733,376th

Spiral-bound: 14 pages
Publisher: Alpha Edition (June 1, 2012)
Language: German
ISBN-10: 3,840,733,383
ISBN-13: 978-3840733383
Size and / or weight: 48.8 x 34.2 x 0.6 cm

", + Sku = "P-1010", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Fast Cars", + Price = 16.95M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productBooksFastCars, "0000946_fast-cars-bildkalender-2013_300.jpeg"); + productBooksFastCars.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cars + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) + }); + productBooksFastCars.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); - //#endregion pictures + #endregion productBooksFastCars - //#region manufacturer + #region productBooksMotorradAbenteuer - ////manufacturer - //productSmartPhonesAppleIphone.ProductManufacturers.Add(new ProductManufacturer() - //{ - // Manufacturer = _ctx.Set().Where(c => c.Name == "Apple").Single(), - // DisplayOrder = 1, - //}); + 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 + }; - //#endregion manufacturer + AddProductPicture(productBooksMotorradAbenteuer, "0000943_motorrad-abenteuer-fahrtechnik-fur-reise-enduros.jpeg"); + productBooksMotorradAbenteuer.ProductCategories.Add(new ProductCategory { Category = categories["Books"], DisplayOrder = 1 }); - //#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 + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Edition -> bound + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Category -> cars + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 9) + }); + productBooksMotorradAbenteuer.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 3, + // Language -> German + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); - //#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"); - #region product Books Stone of the Wise - var productBooksStoneOfTheWise = new Product() + var productBooksStoneOfTheWise = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -12917,58 +12001,37 @@ 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 = categories["Digital Products"], DisplayOrder = 1 }); - //attributes - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.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() + SpecificationAttributeOption = specAttributes[13].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.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 == 6).Single() + SpecificationAttributeOption = specAttributes[14].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) }); - productBooksStoneOfTheWise.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productBooksStoneOfTheWise.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() + SpecificationAttributeOption = specAttributes[12].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - #endregion product Books Stone of the Wise @@ -12995,61 +12058,34 @@ 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 }); - - #region pictures - - //pictures - productInstantDownloadVivaldi.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "vivaldi.jpg"), "image/jpeg", GetSeName(productInstantDownloadVivaldi.Name)), - DisplayOrder = 1, - }); - - #endregion pictures + AddProductPicture(productInstantDownloadVivaldi, "vivaldi.jpg"); + productInstantDownloadVivaldi.ProductCategories.Add(new ProductCategory { Category = categories["Digital Products"], DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // mp3 quality > 320 kbit/S - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[18].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadVivaldi.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, // genre > classic - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() + SpecificationAttributeOption = specAttributes[19].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) }); - #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,68 +12107,38 @@ 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 }); + AddProductPicture(productInstantDownloadBeethoven, "Beethoven.jpg"); + productInstantDownloadBeethoven.ProductCategories.Add(new ProductCategory { Category = categories["Digital Products"], 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 - - #region SpecificationAttributes - //attributes - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // mp3 quality > 320 kbit/S - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 18).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[18].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productInstantDownloadBeethoven.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, // genre > classic - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 19).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 6).Single() + SpecificationAttributeOption = specAttributes[19].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 6) }); - #endregion SpecificationAttributes - #endregion Beethoven für Elise #endregion Instant Download Music #region watches - var categoryWatches = this._ctx.Set().First(c => c.Alias == "Watches"); - - #region productTRANSOCEANCHRONOGRAPH - var productTRANSOCEANCHRONOGRAPH = new Product() + var productTRANSOCEANCHRONOGRAPH = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13154,106 +12160,84 @@ 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 - - #region manufacturer + AddProductPicture(productTRANSOCEANCHRONOGRAPH, "product_transocean-chronograph.jpg"); + productTRANSOCEANCHRONOGRAPH.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); - //manufacturer - productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Breitling").Single(), - DisplayOrder = 1, - }); - - #endregion manufacturer + productTRANSOCEANCHRONOGRAPH.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Breitling"], DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // offer > promotion - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, // manufacturer > Breitling - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 18).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 18) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // housing > steel - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // material -> leather - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // Gender -> gentlemen - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // movement -> mechanical, self winding - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // diameter -> 44mm - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTRANSOCEANCHRONOGRAPH.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // closure -> folding clasp - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - #endregion SpecificationAttributes #endregion productTRANSOCEANCHRONOGRAPH #region productTissotT-TouchExpertSolar - var productTissotTTouchExpertSolar = new Product() + var productTissotTTouchExpertSolar = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13267,120 +12251,94 @@ public IList Products() MetaTitle = "Tissot T-Touch Expert Solar", ShowOnHomePage = true, Price = 969.00M, - OldPrice = 990.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() - }; - - 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 + OldPrice = 990.00M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; - #region manufacturer + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar.jpg", "tissot-1"); + AddProductPicture(productTissotTTouchExpertSolar, "product_tissot-t-touch-expert-solar-t091_2.jpg", "tissot-2"); - //manufacturer - productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Tissot").Single(), - DisplayOrder = 1, - }); + productTissotTTouchExpertSolar.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); - #endregion manufacturer + productTissotTTouchExpertSolar.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Tissot"], DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // offer > best price - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 22).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 8).Single() + SpecificationAttributeOption = specAttributes[22].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 8) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, // manufacturer > Tissot - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 17).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 17) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // housing > steel - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // material -> silicone - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 7).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 7) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // Gender -> gentlemen - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // movement -> Automatic, self-winding - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // diameter -> 44mm - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productTissotTTouchExpertSolar.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // closure -> thorn close - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - #endregion SpecificationAttributes #endregion productTissotT-TouchExpertSolar #region productSeikoSRPA49K1 - var productSeikoSRPA49K1 = new Product() + var productSeikoSRPA49K1 = new Product { ProductType = ProductType.SimpleProduct, VisibleIndividually = true, @@ -13402,621 +12360,463 @@ 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 - - //manufacturer - productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Seiko").Single(), - DisplayOrder = 1, - }); + AddProductPicture(productSeikoSRPA49K1, "product_SeikoSRPA49K1.jpg"); + productSeikoSRPA49K1.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); - #endregion manufacturer + productSeikoSRPA49K1.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Seiko"], DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 1, // housing > steel - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // material -> stainless steel - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 2, // manufacturer > Seiko - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 16).Single() + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 16) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // Gender -> gentlemen - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 7).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // movement -> quarz - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 1).Single() + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // closure -> folding clasp - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productSeikoSRPA49K1.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // diameter -> 44mm - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - #endregion SpecificationAttributes #endregion productSeikoSRPA49K1 #region productWatchesCertinaDSPodiumBigSize - var productWatchesCertinaDSPodiumBigSize = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Name = "Certina DS Podium Big Size", - ShortDescription = "C001.617.26.037.00", - FullDescription = "

Since 1888, Certina has maintained an enviable reputation for its excellent watches and reliable movements. From the time of its integration into the SMH (today's Swatch Group) in the early 1980s, every Certina has been equipped with a high-quality ETA movement.

In a quartz watch movement, high-frequency oscillations are generated in a tiny synthetic crystal, then divided down electronically to provide the extreme accuracy of the Certina internal clock. A battery supplies the necessary energy.

The quartz movement is sometimes equipped with an end-of-life (EOL) indicator. When the seconds hand begins moving in four-second increments, the battery should be replaced within two weeks.

An automatic watch movement is driven by a rotor. Arm and wrist movements spin the rotor, which in turn winds the main spring. Energy is continuously produced, eliminating the need for a battery. The rate precision therefore depends on a rigorous manufacturing process and the original calibration, as well as the lifestyle of the user.

Most automatic movements are driven by an offset rotor. To earn the title of chronometer, a watch must be equipped with a movement that has obtained an official rate certificate from the COSC (Contrôle Officiel Suisse des Chronomètres). To obtain this, precision tests in different positions and at different temperatures must be carried out. These tests take place over a 15-day period. Thermocompensated means that the effective temperature inside the watch is measured and taken into account when improving precision. This allows fluctuations in the rate precision of a normal quartz watch due to temperature variations to be reduced by several seconds a week. The precision is 20 times more accurate than on a normal quartz watch, i.e. +/- 10 seconds per year (0.07 seconds/day).

", + var productWatchesCertinaDSPodiumBigSize = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Name = "Certina DS Podium Big Size", + ShortDescription = "C001.617.26.037.00", + FullDescription = "

Since 1888, Certina has maintained an enviable reputation for its excellent watches and reliable movements. From the time of its integration into the SMH (today's Swatch Group) in the early 1980s, every Certina has been equipped with a high-quality ETA movement.

In a quartz watch movement, high-frequency oscillations are generated in a tiny synthetic crystal, then divided down electronically to provide the extreme accuracy of the Certina internal clock. A battery supplies the necessary energy.

The quartz movement is sometimes equipped with an end-of-life (EOL) indicator. When the seconds hand begins moving in four-second increments, the battery should be replaced within two weeks.

An automatic watch movement is driven by a rotor. Arm and wrist movements spin the rotor, which in turn winds the main spring. Energy is continuously produced, eliminating the need for a battery. The rate precision therefore depends on a rigorous manufacturing process and the original calibration, as well as the lifestyle of the user.

Most automatic movements are driven by an offset rotor. To earn the title of chronometer, a watch must be equipped with a movement that has obtained an official rate certificate from the COSC (Contrôle Officiel Suisse des Chronomètres). To obtain this, precision tests in different positions and at different temperatures must be carried out. These tests take place over a 15-day period. Thermocompensated means that the effective temperature inside the watch is measured and taken into account when improving precision. This allows fluctuations in the rate precision of a normal quartz watch due to temperature variations to be reduced by several seconds a week. The precision is 20 times more accurate than on a normal quartz watch, i.e. +/- 10 seconds per year (0.07 seconds/day).

", Sku = "P-9004", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Certina DS Podium Big Size", - ShowOnHomePage = true, - Price = 479.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() - }; - - 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 - - #region manufacturer + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Certina DS Podium Big Size", + ShowOnHomePage = true, + Price = 479.00M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = thirdDeliveryTime.Id + }; - //manufacturer - productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer() - { - Manufacturer = _ctx.Set().Where(c => c.Name == "Certina").Single(), - DisplayOrder = 1, - }); + AddProductPicture(productWatchesCertinaDSPodiumBigSize, "product_certina_ds_podium_big.png"); + productWatchesCertinaDSPodiumBigSize.ProductCategories.Add(new ProductCategory { Category = categories["Watches"], DisplayOrder = 1 }); - #endregion manufacturer + productWatchesCertinaDSPodiumBigSize.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Certina"], DisplayOrder = 1 }); - #region SpecificationAttributes - //attributes - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 1, - // 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, + DisplayOrder = 1, + // housing > steel + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // material -> leather - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 8).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 5).Single() + SpecificationAttributeOption = specAttributes[8].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 5) }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 2, - // manufacturer > Certina - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 20).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 14).Single() - }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() - { - AllowFiltering = true, - ShowOnProductPage = true, - DisplayOrder = 5, - // 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, + DisplayOrder = 2, + // manufacturer > Certina + SpecificationAttributeOption = specAttributes[20].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 14) + }); + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute + { + AllowFiltering = true, + ShowOnProductPage = true, + DisplayOrder = 5, + // Gender -> gentlemen + SpecificationAttributeOption = specAttributes[7].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 1) + }); + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // movement -> quarz - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 9).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 3).Single() + SpecificationAttributeOption = specAttributes[9].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 3) }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // closure -> folding clasp - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 25).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + SpecificationAttributeOption = specAttributes[25].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute() + productWatchesCertinaDSPodiumBigSize.ProductSpecificationAttributes.Add(new ProductSpecificationAttribute { AllowFiltering = true, ShowOnProductPage = true, DisplayOrder = 5, // diameter -> 40mm - SpecificationAttributeOption = _ctx.Set().Where(sa => sa.DisplayOrder == 24).Single().SpecificationAttributeOptions.Where(sao => sao.DisplayOrder == 2).Single() + SpecificationAttributeOption = specAttributes[24].SpecificationAttributeOptions.FirstOrDefault(x => x.DisplayOrder == 2) }); - #endregion SpecificationAttributes #endregion productWatchesCertinaDSPodiumBigSize - #endregion watches + #endregion watches #region gaming var manuSony = _ctx.Set().First(c => c.Name == "Sony"); + var manuMicrosoft = _ctx.Set().First(c => c.Name == "Microsoft"); 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() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS399000", - Name = "Playstation 4 Pro", - ShortDescription = "The Sony PlayStation 4 Pro is the multi media console for next-generation digital home entertainment. It offers the Blu-ray technology, which enables you to enjoy movies in high definition.", - FullDescription = "
  • PowerPC-base Core @5.2GHz
  • 1 VMX vector unit per core
  • 512KB L2 cache
  • 7 x SPE @5.2GHz
  • 7 x 128b 128 SIMD GPRs
  • 7 x 256MB SRAM for SPE
  • * 1 of 8 SPEs reserved for redundancy total floating point performance: 218 GFLOPS
  • 1.8 TFLOPS floating point performance
  • Full HD (up to 1080p) x 2 channels
  • Multi-way programmable parallel floating point shader pipelines
  • GPU: RSX @550MHz
  • 256MB XDR Main RAM @3.2GHz
  • 256MB GDDR3 VRAM @700MHz
  • Sound: Dolby 5.1ch, DTS, LPCM, etc. (Cell-base processing)
  • Wi-Fi: IEEE 802.11 b/g
  • USB: Front x 4, Rear x 2 (USB2.0)
  • Memory Stick: standard/Duo, PRO x 1
", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, + var productPs3 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Sku = "Sony-PS399000", + Name = "Playstation 4 Pro", + ShortDescription = "The Sony PlayStation 4 Pro is the multi media console for next-generation digital home entertainment. It offers the Blu-ray technology, which enables you to enjoy movies in high definition.", + FullDescription = "
  • PowerPC-base Core @5.2GHz
  • 1 VMX vector unit per core
  • 512KB L2 cache
  • 7 x SPE @5.2GHz
  • 7 x 128b 128 SIMD GPRs
  • 7 x 256MB SRAM for SPE
  • * 1 of 8 SPEs reserved for redundancy total floating point performance: 218 GFLOPS
  • 1.8 TFLOPS floating point performance
  • Full HD (up to 1080p) x 2 channels
  • Multi-way programmable parallel floating point shader pipelines
  • GPU: RSX @550MHz
  • 256MB XDR Main RAM @3.2GHz
  • 256MB GDDR3 VRAM @700MHz
  • Sound: Dolby 5.1ch, DTS, LPCM, etc. (Cell-base processing)
  • Wi-Fi: IEEE 802.11 b/g
  • USB: Front x 4, Rear x 2 (USB2.0)
  • Memory Stick: standard/Duo, PRO x 1
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, //MetaTitle = "Playstation 4 Super Slim", MetaTitle = "Playstation 4 Pro", Price = 189.00M, - OldPrice = 199.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productPs3.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs3.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 4 }); + OldPrice = 199.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; - 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 = manufacturers["Sony"], DisplayOrder = 1 }); + productPs3.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 4 }); - var productDualshock4Controller = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS399004", - Name = "DUALSHOCK 4 Wireless Controller", - ShortDescription = "Revolutionary. Intuitive. Precise. A revolutionary controller for a new era of gaming, the DualShock 4 Wireless Controller features familiar PlayStation controls and innovative new additions, such as a touch pad, light bar, and more.", - FullDescription = "
  • Precision Controller for PlayStation 4: The feel, shape, and sensitivity of the DualShock 4’s analog sticks and trigger buttons have been enhanced to offer players absolute control for all games
  • Sharing at your Fingertips: The addition of the Share button makes sharing your greatest gaming moments as easy as a push of a button. Upload gameplay videos and screenshots directly from your system or live-stream your gameplay, all without disturbing the game in progress.
  • New ways to Play: Revolutionary features like the touch pad, integrated light bar, and built-in speaker offer exciting new ways to experience and interact with your games and its 3.5mm audio jack offers a practical personal audio solution for gamers who want to listen to their games in private.
  • Charge Efficiently: The DualShock 4 Wireless Controller can easily be recharged by plugging it into your PlayStation 4 system, even when on standby, or with any standard charger with a micro-USB port.
", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "DUALSHOCK 4 Wireless Controller", - Price = 54.90M, + var productDualshock4Controller = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Sku = "Sony-PS399004", + Name = "DUALSHOCK 4 Wireless Controller", + ShortDescription = "Revolutionary. Intuitive. Precise. A revolutionary controller for a new era of gaming, the DualShock 4 Wireless Controller features familiar PlayStation controls and innovative new additions, such as a touch pad, light bar, and more.", + FullDescription = "
  • Precision Controller for PlayStation 4: The feel, shape, and sensitivity of the DualShock 4’s analog sticks and trigger buttons have been enhanced to offer players absolute control for all games
  • Sharing at your Fingertips: The addition of the Share button makes sharing your greatest gaming moments as easy as a push of a button. Upload gameplay videos and screenshots directly from your system or live-stream your gameplay, all without disturbing the game in progress.
  • New ways to Play: Revolutionary features like the touch pad, integrated light bar, and built-in speaker offer exciting new ways to experience and interact with your games and its 3.5mm audio jack offers a practical personal audio solution for gamers who want to listen to their games in private.
  • Charge Efficiently: The DualShock 4 Wireless Controller can easily be recharged by plugging it into your PlayStation 4 system, even when on standby, or with any standard charger with a micro-USB port.
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "DUALSHOCK 4 Wireless Controller", + Price = 54.90M, OldPrice = 59.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - 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 = 1 }); + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + 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 = manufacturers["Sony"], DisplayOrder = 1 }); + productDualshock4Controller.ProductCategories.Add(new ProductCategory { Category = categories["Gaming Accessories"], DisplayOrder = 1 }); - var productMinecraft = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, + var productMinecraft = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, //Sku = "Ubi-acreed3", Sku = "PD-Minecraft4ps4", Name = "Minecraft - Playstation 4 Edition", - ShortDescription = "Third-person action-adventure title set.", - FullDescription = "

Build! Craft! Explore!

The critically acclaimed Minecraft comes to PlayStation 4, offering bigger worlds and greater draw distance than the PS3 and PS Vita editions.

Create your own world, then, build, explore and conquer. When night falls the monsters appear, so be sure to build a shelter before they arrive.

The world is only limited by your imagination! Bigger worlds and greater draw distance than PS3 and PS Vita Editions Includes all features from the PS3 version Import your PS3 and PS Vita worlds to the PS4 Editition.

", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - //MetaTitle = "Assassin's Creed III", + ShortDescription = "Third-person action-adventure title set.", + FullDescription = "

Build! Craft! Explore!

The critically acclaimed Minecraft comes to PlayStation 4, offering bigger worlds and greater draw distance than the PS3 and PS Vita editions.

Create your own world, then, build, explore and conquer. When night falls the monsters appear, so be sure to build a shelter before they arrive.

The world is only limited by your imagination! Bigger worlds and greater draw distance than PS3 and PS Vita Editions Includes all features from the PS3 version Import your PS3 and PS Vita worlds to the PS4 Editition.

", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + //MetaTitle = "Assassin's Creed III", MetaTitle = "Minecraft - Playstation 4 Edition", Price = 49.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - 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 }); + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + 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 = manufacturers["Sony"], DisplayOrder = 1 }); + productMinecraft.ProductCategories.Add(new ProductCategory { Category = categories["Games"], DisplayOrder = 4 }); - var productBundlePs3AssassinCreed = new Product() - { - ProductType = ProductType.BundledProduct, - VisibleIndividually = true, - Sku = "Sony-PS399105", - Name = "PlayStation 4 Minecraft Bundle", - ShortDescription = "100GB PlayStation®4 system, 2 × DUALSHOCK®4 wireless controller and Minecraft for PS4 Edition.", - FullDescription = - "
  • Processor

    • Processor Technology : Cell Broadband Engine™
  • General

    • Communication : Ethernet (10BASE-T, 100BASE-TX, 1000BASE-T IEEE 802.11 b/g Wi-Fi
      Bluetooth 2.0 (EDR)
    • Inputs and Outputs : USB 2.0 X 2
  • Graphics

    • Graphics Processor : RSX
  • Memory

    • Internal Memory : 256MB XDR Main RAM
      256MB GDDR3 VRAM
  • Power

    • Power Consumption (in Operation) : Approximately 250 watts
  • Storage

    • Storage Capacity : 2.5' Serial ATA (500GB)
  • Video

    • Resolution : 480i, 480p, 720p, 1080i, 1080p (24p/60p)
  • Weights and Measurements

    • Dimensions (Approx.) : Approximately 11.42\" (W) x 2.56\" (H) x 11.42\" (D) (290mm x 65mm x 290mm)
    • Weight (Approx.) : Approximately 7.055 lbs (3.2 kg)
", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, + var productBundlePs3AssassinCreed = new Product + { + ProductType = ProductType.BundledProduct, + VisibleIndividually = true, + Sku = "Sony-PS399105", + Name = "PlayStation 4 Minecraft Bundle", + ShortDescription = "100GB PlayStation®4 system, 2 × DUALSHOCK®4 wireless controller and Minecraft for PS4 Edition.", + FullDescription = + "
  • Processor

    • Processor Technology : Cell Broadband Engine™
  • General

    • Communication : Ethernet (10BASE-T, 100BASE-TX, 1000BASE-T IEEE 802.11 b/g Wi-Fi
      Bluetooth 2.0 (EDR)
    • Inputs and Outputs : USB 2.0 X 2
  • Graphics

    • Graphics Processor : RSX
  • Memory

    • Internal Memory : 256MB XDR Main RAM
      256MB GDDR3 VRAM
  • Power

    • Power Consumption (in Operation) : Approximately 250 watts
  • Storage

    • Storage Capacity : 2.5' Serial ATA (500GB)
  • Video

    • Resolution : 480i, 480p, 720p, 1080i, 1080p (24p/60p)
  • Weights and Measurements

    • Dimensions (Approx.) : Approximately 11.42\" (W) x 2.56\" (H) x 11.42\" (D) (290mm x 65mm x 290mm)
    • Weight (Approx.) : Approximately 7.055 lbs (3.2 kg)
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, MetaTitle = "PlayStation 4 Minecraft Bundle", Price = 269.97M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - 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 - }); - - #endregion bundlePs3AssassinCreed - - #region bundlePs4 - - var productPs4 = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS410034", - //Sku = "PS410037", - - Name = "PlayStation 4", - ShortDescription = "The best place to play. Working with some of the most creative minds in the industry, PlayStation®4 delivers breathtaking and unique gaming experiences.", - FullDescription = "

The power to perform.

PlayStation®4 was designed from the ground up to ensure that game creators can unleash their imaginations to develop the very best games and deliver new play experiences never before possible. With ultra-fast customized processors and 8GB of high-performance unified system memory, PS4™ is the home to games with rich, high-fidelity graphics and deeply immersive experiences that shatter expectations.

  • Processor

    • Processor Technology : Low power x86-64 AMD 'Jaguar', 8 cores
  • Software

    • Processor : Single-chip custom processor
  • Display

    • Display Technology : HDMI
      Digital Output (optical)
  • General

    • Ethernet ports x speed : Ethernet (10BASE-T, 100BASE-TX, 1000BASE-T); IEEE 802.11 b/g/n; Bluetooth® 2.1 (EDR)
    • Hard disk : Built-in
  • General Specifications

    • Video : BD 6xCAV
      DVD 8xCAV
  • Graphics

    • Graphics Processor : 1.84 TFLOPS, AMD Radeon™ Graphics Core Next engine
  • Interface

    • I/O Port : Super-Speed USB (USB 3.0), AUX
  • Memory

    • Internal Memory : GDDR5 8GB

", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "PlayStation 4", - Price = 399.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 3, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; - - productPs4.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 5 }); - - 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 - }); - - - //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 }); + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id, + ShowOnHomePage = true, + BundleTitleText = "Bundle includes", + BundlePerItemPricing = true, + BundlePerItemShoppingCart = true + }; - //productDualshock4Controller.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_dualshock4_wirelesscontroller.png"), "image/png", GetSeName(productDualshock4Controller.Name)), - // DisplayOrder = 1 - //}); + AddProductPicture(productBundlePs3AssassinCreed, "ps4_bundle_minecraft.jpg"); + productBundlePs3AssassinCreed.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productBundlePs3AssassinCreed.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 1 }); + #endregion bundlePs3AssassinCreed - var productPs4Camera = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Sony-PS410040", - Name = "PlayStation 4 Camera", - ShortDescription = "Play, challenge and share your epic gaming moments with PlayStation®Camera and your PS4™. Multiplayer is enhanced through immediate, crystal clear audio and picture-in-picture video sharing.", - FullDescription = "
  • When combined with the DualShock 4 Wireless Controller's light bar, the evolutionary 3D depth-sensing technology in the PlayStation Camera allows it to precisely track a player's position in the room.
  • From navigational voice commands to facial recognition, the PlayStation Camera adds incredible innovation to your gaming.
  • Automatically integrate a picture-in-picture video of yourself during gameplay broadcasts, and challenge your friends during play.
  • Never leave a friend hanging or miss a chance to taunt your opponents with voice chat that keeps the conversation going, whether it's between rounds, between games, or just while kicking back.
", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "PlayStation 4 Camera", - Price = 59.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + #region bundlePs4 - productPs4Camera.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productPs4Camera.ProductCategories.Add(new ProductCategory() { Category = categoryGamingAccessories, DisplayOrder = 3 }); + var productPs4 = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Sku = "Sony-PS410034", + //Sku = "PS410037", - productPs4Camera.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "product_sony_ps4_camera.png"), "image/png", GetSeName(productPs4Camera.Name)), - DisplayOrder = 1 - }); + Name = "PlayStation 4", + ShortDescription = "The best place to play. Working with some of the most creative minds in the industry, PlayStation®4 delivers breathtaking and unique gaming experiences.", + FullDescription = "

The power to perform.

PlayStation®4 was designed from the ground up to ensure that game creators can unleash their imaginations to develop the very best games and deliver new play experiences never before possible. With ultra-fast customized processors and 8GB of high-performance unified system memory, PS4™ is the home to games with rich, high-fidelity graphics and deeply immersive experiences that shatter expectations.

  • Processor

    • Processor Technology : Low power x86-64 AMD 'Jaguar', 8 cores
  • Software

    • Processor : Single-chip custom processor
  • Display

    • Display Technology : HDMI
      Digital Output (optical)
  • General

    • Ethernet ports x speed : Ethernet (10BASE-T, 100BASE-TX, 1000BASE-T); IEEE 802.11 b/g/n; Bluetooth® 2.1 (EDR)
    • Hard disk : Built-in
  • General Specifications

    • Video : BD 6xCAV
      DVD 8xCAV
  • Graphics

    • Graphics Processor : 1.84 TFLOPS, AMD Radeon™ Graphics Core Next engine
  • Interface

    • I/O Port : Super-Speed USB (USB 3.0), AUX
  • Memory

    • Internal Memory : GDDR5 8GB

", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "PlayStation 4", + Price = 399.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 3, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; + AddProductPicture(productPs4, "product_sony_ps4.png", "sony-ps4"); + AddProductPicture(productPs4, "product_sony_dualshock4_wirelesscontroller.png", "sony-ps4-dualshock"); - var productBundlePs4 = new Product() - { - ProductType = ProductType.BundledProduct, - VisibleIndividually = true, - Sku = "Sony-PS410099", - Name = "PlayStation 4 Bundle", - ShortDescription = "PlayStation®4 system, DUALSHOCK®4 wireless controller and PS4 camera.", - FullDescription = - "

The best place to play

PlayStation 4 is the best place to play with dynamic, connected gaming, powerful graphics and speed, intelligent personalization, deeply integrated social capabilities, and innovative second-screen features. Combining unparalleled content, immersive gaming experiences, all of your favorite digital entertainment apps, and PlayStation exclusives, PS4 centers on gamers, enabling them to play when, where and how they want. PS4 enables the greatest game developers in the world to unlock their creativity and push the boundaries of play through a system that is tuned specifically to their needs.

Gamer focused, developer inspired

The PS4 system focuses on the gamer, ensuring that the very best games and the most immersive experiences are possible on the platform. The PS4 system enables the greatest game developers in the world to unlock their creativity and push the boundaries of play through a system that is tuned specifically to their needs. The PS4 system is centered around a powerful custom chip that contains eight x86-64 cores and a state of the art 1.84 TFLOPS graphics processor with 8 GB of ultra-fast GDDR5 unified system memory, easing game creation and increasing the richness of content achievable on the platform. The end result is new games with rich, high-fidelity graphics and deeply immersive experiences.

Personalized, curated content

The PS4 system has the ability to learn about your preferences. It will learn your likes and dislikes, allowing you to discover content pre-loaded and ready to go on your console in your favorite game genres or by your favorite creators. Players also can look over game-related information shared by friends, view friends’ gameplay with ease, or obtain information about recommended content, including games, TV shows and movies.

New DUALSHOCK controller

DUALSHOCK 4 features new innovations to deliver more immersive gaming experiences, including a highly sensitive six-axis sensor as well as a touch pad located on the top of the controller, which offers gamers completely new ways to play and interact with games.

Shared game experiences

Engage in endless personal challenges with your community and share your epic triumphs with the press of a button. Simply hit the SHARE button on the controller, scan through the last few minutes of gameplay, tag it and return to the game—the video uploads as you play. The PS4 system also enhances social spectating by enabling you to broadcast your gameplay in real-time.

Remote play

Remote Play on the PS4 system fully unlocks the PlayStation Vita system’s potential, making it the ultimate companion device. With the PS Vita system, gamers will be able to seamlessly play a range of PS4 titles on the beautiful 5-inch display over Wi-Fi access points in a local area network.

PlayStation app

The PlayStation App will enable iPhone, iPad, and Android-based smartphones and tablets to become second screens for the PS4 system. Once installed on these devices, players can view in-game items, purchase PS4 games and download them directly to the console at home, or remotely watch the gameplay of other gamers playing on their devices.

PlayStation Plus

Built to bring games and gamers together and fuel the next generation of gaming, PlayStation Plus helps you discover a world of exceptional gaming experiences. PlayStation Plus is a membership service that takes your gaming experience to the next level. Each month members receive an Instant Game Collection of top rated blockbuster and innovative Indie games, which they can download direct to their console.

", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "PlayStation 4 Bundle", - Price = 429.99M, - OldPrice = 449.99M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime, - BundleTitleText = "Bundle includes" - }; + productPs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productPs4.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 5 }); - 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 - }); + var productPs4Camera = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Sku = "Sony-PS410040", + Name = "PlayStation 4 Camera", + ShortDescription = "Play, challenge and share your epic gaming moments with PlayStation®Camera and your PS4™. Multiplayer is enhanced through immediate, crystal clear audio and picture-in-picture video sharing.", + FullDescription = "
  • When combined with the DualShock 4 Wireless Controller's light bar, the evolutionary 3D depth-sensing technology in the PlayStation Camera allows it to precisely track a player's position in the room.
  • From navigational voice commands to facial recognition, the PlayStation Camera adds incredible innovation to your gaming.
  • Automatically integrate a picture-in-picture video of yourself during gameplay broadcasts, and challenge your friends during play.
  • Never leave a friend hanging or miss a chance to taunt your opponents with voice chat that keeps the conversation going, whether it's between rounds, between games, or just while kicking back.
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "PlayStation 4 Camera", + Price = 59.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id + }; - #endregion bundlePs4 + AddProductPicture(productPs4Camera, "product_sony_ps4_camera.png"); + productPs4Camera.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productPs4Camera.ProductCategories.Add(new ProductCategory { Category = categories["Gaming Accessories"], DisplayOrder = 3 }); - #region groupAccessories - var productGroupAccessories = new Product() - { - ProductType = ProductType.GroupedProduct, - VisibleIndividually = true, - Sku = "Sony-GroupAccessories", - Name = "Accessories for unlimited gaming experience", - ShortDescription = "The future of gaming is now with dynamic, connected gaming, powerful graphics and speed, intelligent personalization, deeply integrated social capabilities, and innovative second-screen features. The brilliant culmination of the most creative minds in the industry, PlayStation®4 delivers a unique gaming environment that will take your breath away.", - FullDescription = "
  • Immerse yourself in a new world of gameplay with powerful graphics and speed.
  • Eliminate lengthy load times of saved games with Suspend mode.
  • Immediately play digital titles without waiting for them to finish downloading thanks to background downloading and updating capability.
  • Instantly share images and videos of your favorite gameplay moments on Facebook with the SHARE button on the DUALSHOCK®4 controller.
  • Broadcast while you play in real-time through Ustream.
", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Accessories for unlimited gaming experience", - Price = 0.0M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 3, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - ShowOnHomePage = true - }; + var productBundlePs4 = new Product + { + ProductType = ProductType.BundledProduct, + VisibleIndividually = true, + Sku = "Sony-PS410099", + Name = "PlayStation 4 Bundle", + ShortDescription = "PlayStation®4 system, DUALSHOCK®4 wireless controller and PS4 camera.", + FullDescription = + "

The best place to play

PlayStation 4 is the best place to play with dynamic, connected gaming, powerful graphics and speed, intelligent personalization, deeply integrated social capabilities, and innovative second-screen features. Combining unparalleled content, immersive gaming experiences, all of your favorite digital entertainment apps, and PlayStation exclusives, PS4 centers on gamers, enabling them to play when, where and how they want. PS4 enables the greatest game developers in the world to unlock their creativity and push the boundaries of play through a system that is tuned specifically to their needs.

Gamer focused, developer inspired

The PS4 system focuses on the gamer, ensuring that the very best games and the most immersive experiences are possible on the platform. The PS4 system enables the greatest game developers in the world to unlock their creativity and push the boundaries of play through a system that is tuned specifically to their needs. The PS4 system is centered around a powerful custom chip that contains eight x86-64 cores and a state of the art 1.84 TFLOPS graphics processor with 8 GB of ultra-fast GDDR5 unified system memory, easing game creation and increasing the richness of content achievable on the platform. The end result is new games with rich, high-fidelity graphics and deeply immersive experiences.

Personalized, curated content

The PS4 system has the ability to learn about your preferences. It will learn your likes and dislikes, allowing you to discover content pre-loaded and ready to go on your console in your favorite game genres or by your favorite creators. Players also can look over game-related information shared by friends, view friends’ gameplay with ease, or obtain information about recommended content, including games, TV shows and movies.

New DUALSHOCK controller

DUALSHOCK 4 features new innovations to deliver more immersive gaming experiences, including a highly sensitive six-axis sensor as well as a touch pad located on the top of the controller, which offers gamers completely new ways to play and interact with games.

Shared game experiences

Engage in endless personal challenges with your community and share your epic triumphs with the press of a button. Simply hit the SHARE button on the controller, scan through the last few minutes of gameplay, tag it and return to the game—the video uploads as you play. The PS4 system also enhances social spectating by enabling you to broadcast your gameplay in real-time.

Remote play

Remote Play on the PS4 system fully unlocks the PlayStation Vita system’s potential, making it the ultimate companion device. With the PS Vita system, gamers will be able to seamlessly play a range of PS4 titles on the beautiful 5-inch display over Wi-Fi access points in a local area network.

PlayStation app

The PlayStation App will enable iPhone, iPad, and Android-based smartphones and tablets to become second screens for the PS4 system. Once installed on these devices, players can view in-game items, purchase PS4 games and download them directly to the console at home, or remotely watch the gameplay of other gamers playing on their devices.

PlayStation Plus

Built to bring games and gamers together and fuel the next generation of gaming, PlayStation Plus helps you discover a world of exceptional gaming experiences. PlayStation Plus is a membership service that takes your gaming experience to the next level. Each month members receive an Instant Game Collection of top rated blockbuster and innovative Indie games, which they can download direct to their console.

", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "PlayStation 4 Bundle", + Price = 429.99M, + OldPrice = 449.99M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + DeliveryTimeId = firstDeliveryTime.Id, + BundleTitleText = "Bundle includes" + }; - productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuSony, DisplayOrder = 1 }); - productGroupAccessories.ProductCategories.Add(new ProductCategory() { Category = categoryGaming, DisplayOrder = 3 }); + AddProductPicture(productBundlePs4, "product_sony_ps4_bundle.png"); + productBundlePs4.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productBundlePs4.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 2 }); - productGroupAccessories.ProductPictures.Add(new ProductPicture() - { - Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "category_gaming_accessories.png"), "image/png", GetSeName(productGroupAccessories.Name)), - DisplayOrder = 1 - }); + #endregion bundlePs4 - #endregion groupAccessories + #region groupAccessories - #region Ps3PlusOneGame + var productGroupAccessories = new Product + { + ProductType = ProductType.GroupedProduct, + VisibleIndividually = true, + Sku = "Sony-GroupAccessories", + Name = "Accessories for unlimited gaming experience", + ShortDescription = "The future of gaming is now with dynamic, connected gaming, powerful graphics and speed, intelligent personalization, deeply integrated social capabilities, and innovative second-screen features. The brilliant culmination of the most creative minds in the industry, PlayStation®4 delivers a unique gaming environment that will take your breath away.", + FullDescription = "
  • Immerse yourself in a new world of gameplay with powerful graphics and speed.
  • Eliminate lengthy load times of saved games with Suspend mode.
  • Immediately play digital titles without waiting for them to finish downloading thanks to background downloading and updating capability.
  • Instantly share images and videos of your favorite gameplay moments on Facebook with the SHARE button on the DUALSHOCK®4 controller.
  • Broadcast while you play in real-time through Ustream.
", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Accessories for unlimited gaming experience", + Price = 0.0M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 3, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + ShowOnHomePage = true + }; - //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 - //}; + AddProductPicture(productGroupAccessories, "category_gaming_accessories.png"); + productGroupAccessories.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manufacturers["Sony"], DisplayOrder = 1 }); + productGroupAccessories.ProductCategories.Add(new ProductCategory { Category = categories["Gaming"], DisplayOrder = 3 }); - //productWatchDogs.ProductManufacturers.Add(new ProductManufacturer() { Manufacturer = manuUbisoft, DisplayOrder = 1 }); - //productWatchDogs.ProductCategories.Add(new ProductCategory() { Category = categoryGamingGames, DisplayOrder = 1 }); + #endregion groupAccessories - //productWatchDogs.ProductPictures.Add(new ProductPicture() - //{ - // Picture = CreatePicture(File.ReadAllBytes(sampleImagesPath + "ubisoft-watchdogs.jpg"), "image/jpeg", GetSeName(productWatchDogs.Name)), - // DisplayOrder = 1 - //}); + #region Ps3PlusOneGame - var productPrinceOfPersia = new Product() - { - ProductType = ProductType.SimpleProduct, - VisibleIndividually = true, - Sku = "Ubi-princepersia", - Name = "Prince of Persia \"The Forgotten Sands\"", - ShortDescription = "Play the epic story of the heroic Prince as he fights and outwits his enemies in order to save his kingdom.", - FullDescription = "

This game marks the return to the Prince of Persia® Sands of Time storyline. Prince of Persia: The Forgotten Sands™ will feature many of the fan-favorite elements from the original series as well as new gameplay innovations that gamers have come to expect from Prince of Persia.

Experience the story, setting, and gameplay in this return to the Sands of Time universe as we follow the original Prince of Persia through a new untold chapter.

Created by Ubisoft Montreal, the team that brought you various Prince of Persia® and Assassin’s Creed® games, Prince of Persia The Forgotten Sands™ has been over 2 years in the making.

", - ProductTemplateId = productTemplate.Id, - AllowCustomerReviews = true, - Published = true, - MetaTitle = "Prince of Persia", - Price = 39.90M, - ManageInventoryMethod = ManageInventoryMethod.DontManageStock, - OrderMinimumQuantity = 1, - OrderMaximumQuantity = 10000, - StockQuantity = 10000, - NotifyAdminForQuantityBelow = 1, - AllowBackInStockSubscriptions = false, - IsShipEnabled = true, - DeliveryTime = firstDeliveryTime - }; + var productPrinceOfPersia = new Product + { + ProductType = ProductType.SimpleProduct, + VisibleIndividually = true, + Sku = "Ubi-princepersia", + Name = "Prince of Persia \"The Forgotten Sands\"", + ShortDescription = "Play the epic story of the heroic Prince as he fights and outwits his enemies in order to save his kingdom.", + FullDescription = "

This game marks the return to the Prince of Persia® Sands of Time storyline. Prince of Persia: The Forgotten Sands™ will feature many of the fan-favorite elements from the original series as well as new gameplay innovations that gamers have come to expect from Prince of Persia.

Experience the story, setting, and gameplay in this return to the Sands of Time universe as we follow the original Prince of Persia through a new untold chapter.

Created by Ubisoft Montreal, the team that brought you various Prince of Persia® and Assassin’s Creed® games, Prince of Persia The Forgotten Sands™ has been over 2 years in the making.

", + ProductTemplateId = productTemplate.Id, + AllowCustomerReviews = true, + Published = true, + MetaTitle = "Prince of Persia", + Price = 39.90M, + ManageInventoryMethod = ManageInventoryMethod.DontManageStock, + OrderMinimumQuantity = 1, + OrderMaximumQuantity = 10000, + StockQuantity = 10000, + NotifyAdminForQuantityBelow = 1, + AllowBackInStockSubscriptions = false, + IsShipEnabled = true, + 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 = manufacturers["Ubisoft"], DisplayOrder = 1 }); + productPrinceOfPersia.ProductCategories.Add(new ProductCategory { Category = categories["Games"], 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 +12838,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 = manufacturers["Sony"], DisplayOrder = 1 }); + productHorizonZeroDown.ProductCategories.Add(new ProductCategory { Category = categories["Games"], 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 +12871,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 = manufacturers["EA Sports"], DisplayOrder = 1 }); + productFifa17.ProductCategories.Add(new ProductCategory { Category = categories["Games"], 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,74 +12903,67 @@ 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 = manufacturers["Warner Home Video Games"], DisplayOrder = 1 }); + productLegoWorlds.ProductCategories.Add(new ProductCategory { Category = categories["Games"], 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() + //#region XBox One S + + //var productXBoxOneS = 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, + // Sku = "Microsoft-xbox1s", + // Name = "Xbox One S 500 GB Konsole", + // ShortDescription = "Genieße über 100 Spiele, die es nur für die Konsole gibt, sowie eine ständig größer werdende Bibliothek an Xbox 360-Spielen auf der Xbox One S im neuen Design – der einzigen Konsole mit 4K Ultra HD Blu-ray, 4K-Videostreaming und HDR. Streame deine Lieblingsfilme und -sendungen in atemberaubendem 4K Ultra HD. Spiele Blockbuster wie Gears of War 4 und Battlefield 1 mit Freunden auf Xbox Live, dem schnellsten und zuverlässigsten Gaming-Netzwerk.", + // FullDescription = "
  • Die ultimativen Spiele und 4K-Entertainment-System.
  • 40 % kompaktere Konsole
    Lasse dich nicht von der Größe täuschen. Mit integriertem Netzteil und bis zu 2 TB Speicherplatz ist die Xbox One S die fortschrittlichste Xbox überhaupt.
  • Der beste Controller - jetzt noch besser
    Der neue Xbox Wireless Controller bietet ein schlankes, optimiertes Design, texturierte Grip - Fläche und Bluetooth zum Spielen auf Windows 10 Geräten. Genieße individuelle Tastenbelegung und verbesserte drahtlose Reichweite und stecke jeden kompatiblen Kopfhörer mit der 3, 5 mm Stereo - Headset - Buchse ein.
", // ProductTemplateId = productTemplate.Id, // AllowCustomerReviews = true, // Published = true, - // MetaTitle = "PlayStation 3 plus game cheaper", - // Price = 160.00M, + // MetaTitle = "Xbox One S", + // Price = 279.99M, + // OldPrice = 279.99M, // ManageInventoryMethod = ManageInventoryMethod.DontManageStock, // OrderMinimumQuantity = 1, - // OrderMaximumQuantity = 3, + // OrderMaximumQuantity = 10000, // StockQuantity = 10000, // NotifyAdminForQuantityBelow = 1, // AllowBackInStockSubscriptions = false, // IsShipEnabled = true, - // DeliveryTime = firstDeliveryTime + // DeliveryTimeId = firstDeliveryTime.Id //}; - //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 + //AddProductPicture(productXBoxOneS, "product_xbox_one_s_1.png", "microsoft-xbox-one-s-1"); + //productXBoxOneS.ProductManufacturers.Add(new ProductManufacturer { Manufacturer = manuMicrosoft, DisplayOrder = 1 }); + //productXBoxOneS.ProductCategories.Add(new ProductCategory { Category = categoryGaming, DisplayOrder = 1 }); + //#endregion #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 + productGroupAccessories, productPrinceOfPersia, productLegoWorlds, productHorizonZeroDown, productFifa17, productJackWolfsKinKanukaPoint }; - entities.AddRange(GetFashionProducts()); - entities.AddRange(GetFurnitureProducts()); + entities.AddRange(GetFashionProducts(specAttributes)); + entities.AddRange(GetFurnitureProducts(specAttributes)); this.Alter(entities); return entities; @@ -14321,7 +13106,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 +13191,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,14 +13211,14 @@ public IList ForumGroups() this.Alter(entities); return entities; } - #endregion ForumGroups - #region Forums public IList Forums() { - var newProductsForum = new Forum + var group = _ctx.Set().FirstOrDefault(c => c.DisplayOrder == 1); + + var newProductsForum = new Forum { - ForumGroup = _ctx.Set().Where(c => c.DisplayOrder == 1).Single(), + ForumGroup = group, Name = "New Products", Description = "Discuss new products and industry trends", NumTopics = 0, @@ -14377,7 +13230,7 @@ public IList Forums() var packagingShippingForum = new Forum { - ForumGroup = _ctx.Set().Where(c => c.DisplayOrder == 1).Single(), + ForumGroup = group, Name = "Packaging & Shipping", Description = "Discuss packaging & shipping", NumTopics = 0, @@ -14395,9 +13248,6 @@ public IList Forums() this.Alter(entities); return entities; } - #endregion Forums - - #region Discounts public IList Discounts() { @@ -14433,9 +13283,6 @@ public IList Discounts() return entities; } - #endregion Discounts - - #region Deliverytimes public IList DeliveryTimes() { var entities = new List() @@ -14463,47 +13310,50 @@ public IList DeliveryTimes() return entities; } - #endregion Deliverytimes - - #region QuantityUnits - public IList QuantityUnits() { - var entities = new List() - { - new QuantityUnit - { - Name = "Piece", - Description = "Piece", - IsDefault = true, - DisplayOrder = 0, - }, - new QuantityUnit - { - Name = "Box", - Description = "Box", - DisplayOrder = 1, - }, - new QuantityUnit - { - Name = "Parcel", - Description = "Parcel", - DisplayOrder = 2, - }, - new QuantityUnit - { - Name = "Palette", - Description = "Palette", - DisplayOrder = 3, - }, - }; + var count = 0; + var entities = new List(); + + var quPluralEn = new Dictionary + { + { "Piece", "Pieces" }, + { "Box", "Boxes" }, + { "Parcel", "Parcels" }, + { "Palette", "Pallets" }, + { "Unit", "Units" }, + { "Sack", "Sacks" }, + { "Bag", "Bags" }, + { "Can", "Cans" }, + { "Packet", "Packets" }, + { "Bar", "Bars" }, + { "Bottle", "Bottles" }, + { "Glass", "Glasses" }, + { "Bunch", "Bunches" }, + { "Roll", "Rolls" }, + { "Cup", "Cups" }, + { "Bundle", "Bundles" }, + { "Barrel", "Barrels" }, + { "Set", "Sets" }, + { "Bucket", "Buckets" } + }; + + foreach (var qu in quPluralEn) + { + entities.Add(new QuantityUnit + { + Name = qu.Key, + NamePlural = qu.Value, + Description = qu.Key, + IsDefault = qu.Key == "Piece", + DisplayOrder = count++ + }); + } + this.Alter(entities); return entities; } - #endregion - - #region BlogPost public IList BlogPosts() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14522,7 +13372,7 @@ public IList BlogPosts() Language = defaultLanguage, Title = "Customer Service - Client Service", Body = "

Managing online business requires different skills and abilities than managing a business in the ‘real world.’ Customers can easily detect the size and determine the prestige of a business when they have the ability to walk in and take a look around. Not only do ‘real-world’ furnishings and location tell the customer what level of professionalism to expect, but "real world" personal encounters allow first impressions to be determined by how the business approaches its customer service. When a customer walks into a retail business just about anywhere in the world, that customer expects prompt and personal service, especially with regards to questions that they may have about products they wish to purchase.

Customer service or the client service is the service provided to the customer for his satisfaction during and after the purchase. It is necessary to every business organization to understand the customer needs for value added service. So customer data collection is essential. For this, a good customer service is important. The easiest way to lose a client is because of the poor customer service. The importance of customer service changes by product, industry and customer. Client service is an important part of every business organization. Each organization is different in its attitude towards customer service. Customer service requires a superior quality service through a careful design and execution of a series of activities which include people, technology and processes. Good customer service starts with the design and communication between the company and the staff.

In some ways, the lack of a physical business location allows the online business some leeway that their ‘real world’ counterparts do not enjoy. Location is not important, furnishings are not an issue, and most of the visual first impression is made through the professional design of the business website.

However, one thing still remains true. Customers will make their first impressions on the customer service they encounter. Unfortunately, in online business there is no opportunity for front- line staff to make a good impression. Every interaction the customer has with the website will be their primary means of making their first impression towards the business and its client service. Good customer service in any online business is a direct result of good website design and planning.

By Jayashree Pakhare (buzzle.com)

", - Tags = "e-commerce, SmartStore.NET, asp.net, sample tag, money", + Tags = "e-commerce, Smartstore.NET, asp.net, sample tag, money", CreatedOnUtc = DateTime.UtcNow.AddSeconds(1), }; @@ -14534,9 +13384,7 @@ public IList BlogPosts() this.Alter(entities); return entities; } - #endregion BlogPost - #region NewsItems public IList NewsItems() { var defaultLanguage = _ctx.Set().FirstOrDefault(); @@ -14544,26 +13392,26 @@ public IList NewsItems() { AllowComments = true, Language = defaultLanguage, - Title = "SmartStore.NET new release!", - Short = "SmartStore.NET includes everything you need to begin your e-commerce online store.", - Full = "

SmartStore.NET includes everything you need to begin your e-commerce online store.
We have thought of everything and it's all included!

From downloads to documentation, www.smartstore.com offers a comprehensive base of information, resources, and support to the SmartStore.NET community.

", + Title = "Smartstore.NET 3.2 now with the new CMS Page Builder", + Short = "Create fascinating content with the new Page Builder", + Full = "

Create fascinating content from products, product groups, images, videos and texts.
Transitions, animations, gradients, hover effects or overlays are easily applied in the WYSIWYG editor.

More information about Smartstore 3.2 and Page Builder can be found at www.smartstore.com

", Published = true, - MetaTitle = "SmartStore.NET new release!", + MetaTitle = "Smartstore.NET 3.2", CreatedOnUtc = DateTime.Now }; var news2 = new NewsItem() - { - AllowComments = true, - Language = defaultLanguage, - Title = "New online store is open!", - Short = "The new SmartStore.NET store is open now! We are very excited to offer our new range of products. We will be constantly adding to our range so please register on our site, this will enable you to keep up to date with any new products.", - Full = "

Our online store is officially up and running. Stock up for the holiday season! We have a great selection of items. We will be constantly adding to our range so please register on our site, this will enable you to keep up to date with any new products.

All shipping is worldwide and will leave the same day an order is placed! Happy Shopping and spread the word!!

", - Published = true, - MetaTitle = "New online store is open!", - CreatedOnUtc = DateTime.Now - }; + { + AllowComments = true, + Language = defaultLanguage, + Title = "Smartstore.NET new release!", + Short = "Smartstore.NET includes everything you need to begin your e-commerce online store.", + Full = "

Smartstore.NET includes everything you need to begin your e-commerce online store.
We have thought of everything and it's all included!

From downloads to documentation, www.smartstore.com offers a comprehensive base of information, resources, and support to the Smartstore.NET community.

", + Published = true, + MetaTitle = "Smartstore.NET new release!", + CreatedOnUtc = DateTime.Now + }; - var entities = new List + var entities = new List { news1, news2 }; @@ -14571,9 +13419,7 @@ public IList NewsItems() this.Alter(entities); return entities; } - #endregion NewsItems - #region PollAnswer public IList PollAnswers() { var pollAnswer1 = new PollAnswer() @@ -14625,89 +13471,77 @@ public IList PollAnswers() this.Alter(entities); return entities; } - #endregion PollAnswer - #region Polls public IList Polls() { var defaultLanguage = _ctx.Set().FirstOrDefault(); - var poll1 = new Poll() + var poll1 = new Poll { Language = defaultLanguage, Name = "How do you like the shop?", - SystemKeyword = "RightColumnPoll", + SystemKeyword = "Blog", Published = true, DisplayOrder = 1, }; - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Excellent", DisplayOrder = 1, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Good", DisplayOrder = 2, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Poor", DisplayOrder = 3, }); - poll1.PollAnswers.Add(new PollAnswer() + poll1.PollAnswers.Add(new PollAnswer { Name = "Very bad", DisplayOrder = 4, }); - //_pollAnswerRepository.Table.Where(x => x.DisplayOrder < 5).Each(y => - // { - // poll1.PollAnswers.Add(y); - // }); - - var poll2 = new Poll() + var poll2 = new Poll { Language = defaultLanguage, Name = "How often do you buy online?", - SystemKeyword = "RightColumnPoll", + SystemKeyword = "Blog", Published = true, DisplayOrder = 2, }; - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Daily", DisplayOrder = 1, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Once a week", DisplayOrder = 2, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Every two weeks", DisplayOrder = 3, }); - poll2.PollAnswers.Add(new PollAnswer() + poll2.PollAnswers.Add(new PollAnswer { Name = "Once a month", DisplayOrder = 4, }); - //_pollAnswerRepository.Table.Where(x => x.DisplayOrder > 4).Each(y => - //{ - // poll2.PollAnswers.Add(y); - //}); - var entities = new List { @@ -14717,8 +13551,6 @@ public IList Polls() this.Alter(entities); return entities; } - #endregion Polls - #region Alterations @@ -14906,6 +13738,10 @@ protected virtual void Alter(IList entities) { } + protected virtual void Alter(UrlRecord entity) + { + } + #endregion Alterations #endregion Sample data creators @@ -14936,6 +13772,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, true).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 +13837,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 +13874,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/SmartDbConfiguration.cs b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs index 163b21ba8a..d18414d6e9 100644 --- a/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs +++ b/src/Libraries/SmartStore.Data/SmartDbConfiguration.cs @@ -6,6 +6,9 @@ using SmartStore.Core.Infrastructure; using SmartStore.Data.Caching; using System.Web.Hosting; +using System.Data.Entity.Infrastructure; +using System.Data.Entity.Infrastructure.DependencyResolution; +using SmartStore.Utilities; namespace SmartStore.Data { @@ -13,6 +16,15 @@ public class SmartDbConfiguration : DbConfiguration { public SmartDbConfiguration() { + // DB model caching + if (HostingEnvironment.IsHosted) + { + var cacheLocation = CommonHelper.MapPath("~/App_Data/EfCache"); + System.IO.Directory.CreateDirectory(cacheLocation); + var modelStore = new EfDbModelStore(cacheLocation); + AddDependencyResolver(new SingletonDependencyResolver(modelStore)); + } + IEfDataProvider provider = null; try { @@ -22,7 +34,7 @@ public SmartDbConfiguration() { /* SmartStore is not installed yet! */ } - + if (provider != null) { base.SetDefaultConnectionFactory(provider.GetConnectionFactory()); diff --git a/src/Libraries/SmartStore.Data/SmartObjectContext.cs b/src/Libraries/SmartStore.Data/SmartObjectContext.cs index a294110582..586a4f6f3f 100644 --- a/src/Libraries/SmartStore.Data/SmartObjectContext.cs +++ b/src/Libraries/SmartStore.Data/SmartObjectContext.cs @@ -1,19 +1,17 @@ using System; -using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Data.Entity; -using System.Data.Entity.Infrastructure; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Reflection; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Cms; using SmartStore.Data.Migrations; using SmartStore.Data.Setup; namespace SmartStore.Data -{ - public class SmartObjectContext : ObjectContextBase +{ + public class SmartObjectContext : ObjectContextBase { static SmartObjectContext() { @@ -68,6 +66,9 @@ where t.Namespace.HasValue() && //...or do it manually below. For example, //modelBuilder.Configurations.Add(new LanguageMap()); + modelBuilder.Entity(); + modelBuilder.Entity(); + base.OnModelCreating(modelBuilder); } diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 3af5d18d3d..9f7c8ce217 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -33,6 +34,7 @@ prompt 4 false + latest pdbonly @@ -42,6 +44,7 @@ prompt 4 false + latest true @@ -51,6 +54,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest true @@ -60,11 +64,11 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest - - ..\..\packages\Autofac.4.5.0\lib\net45\Autofac.dll - True + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll @@ -90,9 +94,8 @@ True ..\..\packages\Microsoft.SqlServer.Scripting.11.0.2100.61\lib\Microsoft.SqlServer.Smo.dll - - ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll @@ -102,8 +105,8 @@ ..\..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll - - ..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + + ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll @@ -129,6 +132,9 @@ + + + @@ -137,14 +143,17 @@ + + + 201403112331027_Initial.cs @@ -557,6 +566,126 @@ 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 + + + + 201809261026134_ForumGroupAcl.cs + + + + 201810011954195_ForumPostVote.cs + + + + 201810231214068_DataExchangeEnhancements.cs + + + + 201811061745204_IsSystemProductIndex.cs + + + + 201811082148279_LocalizedPropertyKeyGroupIndex.cs + + + + 201811161142587_TaskHistoryErrorLength.cs + + + + 201811202204501_ProductIndexSeekExport.cs + + + + 201902211855242_TopicHtmlIdAndBodyCss.cs + + + + 201904110735029_Menus.cs + + + + 201905020948354_WidgetTopics.cs + + + + 201905101159134_V320Resources.cs + + + + 201905271110370_V321Resources.cs + + + + 201906252008551_QuantityUnitNamePlural.cs + + + + 201907032251575_CategoryExternalLink.cs + + + + 201908150749388_V322Resources.cs + @@ -1009,6 +1138,96 @@ 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 + + + 201809261026134_ForumGroupAcl.cs + + + 201810011954195_ForumPostVote.cs + + + 201810231214068_DataExchangeEnhancements.cs + + + 201811061745204_IsSystemProductIndex.cs + + + 201811082148279_LocalizedPropertyKeyGroupIndex.cs + + + 201811161142587_TaskHistoryErrorLength.cs + + + 201811202204501_ProductIndexSeekExport.cs + + + 201902211855242_TopicHtmlIdAndBodyCss.cs + + + 201904110735029_Menus.cs + + + 201905020948354_WidgetTopics.cs + + + 201905101159134_V320Resources.cs + + + 201905271110370_V321Resources.cs + + + 201906252008551_QuantityUnitNamePlural.cs + + + 201907032251575_CategoryExternalLink.cs + + + 201908150749388_V322Resources.cs + diff --git a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql index dbc47e8321..709dabb0ec 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.Inverse.sql @@ -8,4 +8,4 @@ DROP INDEX [IX_PSAM_SpecificationAttributeOptionId_AllowFiltering] ON [Product_S GO DROP INDEX [IX_LocalizedProperty_Key] ON [LocalizedProperty] -GO \ No newline at end of file +GO diff --git a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql index ad39c3e86c..c871635093 100644 --- a/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql +++ b/src/Libraries/SmartStore.Data/Sql/Indexes.SqlServer.sql @@ -8,4 +8,4 @@ CREATE NONCLUSTERED INDEX [IX_PSAM_SpecificationAttributeOptionId_AllowFiltering GO CREATE NONCLUSTERED INDEX [IX_LocalizedProperty_Key] ON [LocalizedProperty] ([Id]) INCLUDE ([EntityId], [LocaleKeyGroup], [LocaleKey]) -GO \ No newline at end of file +GO diff --git a/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs index 3cb6a0781c..49c93d1b22 100644 --- a/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs +++ b/src/Libraries/SmartStore.Data/Utilities/DataMigrator.cs @@ -1,32 +1,95 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Data.Entity; -using EfState = System.Data.Entity.EntityState; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core; +using SmartStore.Core.Domain.Cms; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Configuration; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.IO; +using SmartStore.Data.Setup; using SmartStore.Utilities; -using System.IO; -using System.Xml.Linq; +using EfState = System.Data.Entity.EntityState; namespace SmartStore.Data.Utilities { - public static class DataMigrator + 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)); + + const string entityName = "Product"; + +#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, 1000); + + foreach (var p in products) + { + try + { + if (downloads.TryGetValue(p.DownloadId, out var download)) + { + download.EntityId = p.Id; + download.EntityName = entityName; + } + } + 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 +136,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 +212,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 +224,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 +334,494 @@ 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 + + #region CreateSystemMenus (V3.2) + + public static void CreateSystemMenus(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)); + } + + const string entityProvider = "entity"; + const string routeProvider = "route"; + const string routeTemplate = "{{\"routename\":\"{0}\"}}"; + + var resourceNames = new string[] { + "Footer.Info", + "Footer.Service", + "Footer.Company", + "Manufacturers.List", + "Admin.Catalog.Categories", + "Products.NewProducts", + "Products.RecentlyViewedProducts", + "Products.Compare.List", + "ContactUs", + "Blog", + "Forum.Forums", + "Account.Login", + "Menu.ServiceMenu" + }; + + var settingNames = new string[] + { + "CatalogSettings.RecentlyAddedProductsEnabled", + "CatalogSettings.RecentlyViewedProductsEnabled", + "CatalogSettings.CompareProductsEnabled", + "BlogSettings.Enabled", + "ForumSettings.ForumsEnabled", + "CustomerSettings.UserRegistrationType" + }; + + Dictionary resources = null; + Dictionary settings = null; + + using (var scope = new DbContextScope(ctx: context, validateOnSave: false, hooksEnabled: false, autoCommit: false)) + { + var permissionMigrator = new PermissionMigrator(ctx); + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Menus", + SystemName = "ManageMenus", + Category = "Content Management" + }, new string[] { SystemCustomerRoleNames.Administrators }); + + var menuSet = context.Set(); + var menuItemSet = context.Set(); + var defaultLang = context.Set().OrderBy(x => x.DisplayOrder).First(); + var manufacturerCount = context.Set().Count(); + var order = 0; + + resources = context.Set().AsNoTracking() + .Where(x => x.LanguageId == defaultLang.Id && resourceNames.Contains(x.ResourceName)) + .Select(x => new { x.ResourceName, x.ResourceValue }) + .ToList() + .ToDictionarySafe(x => x.ResourceName, x => x.ResourceValue, StringComparer.OrdinalIgnoreCase); + + settings = context.Set().AsNoTracking() + .Where(x => x.StoreId == 0 && settingNames.Contains(x.Name)) + .Select(x => new { x.Name, x.Value }) + .ToList() + .ToDictionarySafe(x => x.Name, x => x.Value, StringComparer.OrdinalIgnoreCase); + + #region System menus + + var mainMenu = menuSet.Add(new MenuRecord + { + SystemName = "Main", + IsSystemMenu = true, + Published = true, + Template = "Navbar", + Title = GetResource("Admin.Catalog.Categories") + }); + + var footerInfo = menuSet.Add(new MenuRecord + { + SystemName = "FooterInformation", + IsSystemMenu = true, + Published = true, + Template = "LinkList", + Title = "Footer - " + GetResource("Footer.Info") + }); + + var footerService = menuSet.Add(new MenuRecord + { + SystemName = "FooterService", + IsSystemMenu = true, + Published = true, + Template = "LinkList", + Title = "Footer - " + GetResource("Footer.Service") + }); + + var footerCompany = menuSet.Add(new MenuRecord + { + SystemName = "FooterCompany", + IsSystemMenu = true, + Published = true, + Template = "LinkList", + Title = "Footer - " + GetResource("Footer.Company") + }); + + var serviceMenu = menuSet.Add(new MenuRecord + { + SystemName = "HelpAndService", + IsSystemMenu = true, + Published = true, + Template = "Dropdown", + Title = GetResource("Menu.ServiceMenu").NullEmpty() ?? "Service" + }); + + scope.Commit(); + + #endregion + + #region Main and footer menus + + menuItemSet.Add(new MenuItemRecord + { + MenuId = mainMenu.Id, + ProviderName = "catalog", + Published = true + }); + + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerInfo.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("ManufacturerList"), + Title = GetResource("Manufacturers.List"), + DisplayOrder = ++order, + Published = manufacturerCount > 0 + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerInfo.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("RecentlyAddedProducts"), + Title = GetResource("Products.NewProducts"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.RecentlyAddedProductsEnabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerInfo.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("RecentlyViewedProducts"), + Title = GetResource("Products.RecentlyViewedProducts"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.RecentlyViewedProductsEnabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerInfo.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("CompareProducts"), + Title = GetResource("Products.Compare.List"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.CompareProductsEnabled", true) + }); + + scope.Commit(); + order = 0; + + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerService.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("contactus"), + Title = GetResource("ContactUs"), + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerService.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("Blog"), + Title = GetResource("Blog"), + DisplayOrder = ++order, + Published = GetSetting("BlogSettings.Enabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerService.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("Boards"), + Title = GetResource("Forum.Forums"), + DisplayOrder = ++order, + Published = GetSetting("ForumSettings.ForumsEnabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerService.Id, + ProviderName = entityProvider, + Model = "topic:shippinginfo", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerService.Id, + ProviderName = entityProvider, + Model = "topic:paymentinfo", + DisplayOrder = ++order + }); + + scope.Commit(); + order = 0; + + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = entityProvider, + Model = "topic:aboutus", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = entityProvider, + Model = "topic:imprint", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = entityProvider, + Model = "topic:disclaimer", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = entityProvider, + Model = "topic:privacyinfo", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = entityProvider, + Model = "topic:conditionsofuse", + DisplayOrder = ++order + }); + + if (GetSetting("CustomerSettings.UserRegistrationType", "").IsCaseInsensitiveEqual("Disabled")) + { + menuItemSet.Add(new MenuItemRecord + { + MenuId = footerCompany.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("Login"), + Title = GetResource("Account.Login"), + DisplayOrder = ++order + }); + } + + scope.Commit(); + order = 0; + + #endregion + + #region Help & Service + + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("RecentlyAddedProducts"), + Title = GetResource("Products.NewProducts"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.RecentlyAddedProductsEnabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("ManufacturerList"), + Title = GetResource("Manufacturers.List"), + DisplayOrder = ++order, + Published = manufacturerCount > 0 + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("RecentlyViewedProducts"), + Title = GetResource("Products.RecentlyViewedProducts"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.RecentlyViewedProductsEnabled", true) + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = routeProvider, + Model = routeTemplate.FormatInvariant("CompareProducts"), + Title = GetResource("Products.Compare.List"), + DisplayOrder = ++order, + Published = GetSetting("CatalogSettings.CompareProductsEnabled", true) + }); + + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = entityProvider, + Model = "topic:aboutus", + DisplayOrder = ++order, + BeginGroup = true + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = entityProvider, + Model = "topic:disclaimer", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = entityProvider, + Model = "topic:shippinginfo", + DisplayOrder = ++order + }); + menuItemSet.Add(new MenuItemRecord + { + MenuId = serviceMenu.Id, + ProviderName = entityProvider, + Model = "topic:conditionsofuse", + DisplayOrder = ++order + }); + + scope.Commit(); + order = 0; + + #endregion + + #region Localization + + var resourceSet = context.Set(); + + var removeNames = new List { "Menu.ServiceMenu" }; + var removeResources = resourceSet.Where(x => removeNames.Contains(x.ResourceName)).ToList(); + resourceSet.RemoveRange(removeResources); + + scope.Commit(); + + #endregion + } + + #region Utilities + + string GetResource(string name) + { + return resources.TryGetValue(name, out var value) ? value : string.Empty; + } + + T GetSetting(string name, T defaultValue = default(T)) + { + try + { + if (settings.TryGetValue(name, out var str) && CommonHelper.TryConvert(str, out T value)) + { + return value; + } + } + catch { } + + return defaultValue; + } + + #endregion + } + + #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.Data/app.config b/src/Libraries/SmartStore.Data/app.config index 9e763e6753..63141836b9 100644 --- a/src/Libraries/SmartStore.Data/app.config +++ b/src/Libraries/SmartStore.Data/app.config @@ -4,16 +4,20 @@ - + - + + + + + diff --git a/src/Libraries/SmartStore.Data/packages.config b/src/Libraries/SmartStore.Data/packages.config index fe0a5053cd..a063941a5e 100644 --- a/src/Libraries/SmartStore.Data/packages.config +++ b/src/Libraries/SmartStore.Data/packages.config @@ -1,10 +1,10 @@  - + - - + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs index fe7fd42bfb..7eab4cd87a 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/ExternalAuthorizer.cs @@ -111,6 +111,7 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame result.AddError("Account is already assigned"); return result; } + if (AccountDoesNotExistAndUserIsNotLoggedOn(userFound, userLoggedIn)) { ExternalAuthorizerHelper.StoreParametersForRoundTrip(parameters); @@ -123,21 +124,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); + _customerSettings.CustomerLoginType != CustomerLoginType.Email ? 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); - if (!String.IsNullOrEmpty(details.LastName)) - _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.LastName, details.LastName); + // store other parameters (form fields) + if (details.FirstName.HasValue()) + currentCustomer.FirstName = details.FirstName; + if (details.LastName.HasValue()) + currentCustomer.LastName = details.LastName; - - userFound = currentCustomer; + userFound = currentCustomer; _openAuthenticationService.AssociateExternalAccountWithUser(currentCustomer, parameters); ExternalAuthorizerHelper.RemoveParameters(); @@ -154,27 +153,24 @@ public virtual AuthorizationResult Authorize(OpenAuthenticationParameters parame switch (_customerSettings.UserRegistrationType) { case UserRegistrationType.EmailValidation: - { - // email validation message - _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); - _messageFactory.SendCustomerEmailValidationMessage(currentCustomer, _workContext.WorkingLanguage.Id); - - // result - return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredEmailValidation); - } + { + // email validation message + _genericAttributeService.SaveAttribute(currentCustomer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); + _messageFactory.SendCustomerEmailValidationMessage(currentCustomer, _workContext.WorkingLanguage.Id); + + return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredEmailValidation); + } case UserRegistrationType.AdminApproval: - { - // result - return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredAdminApproval); - } + { + return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredAdminApproval); + } case UserRegistrationType.Standard: - { - // send customer welcome message - _messageFactory.SendCustomerWelcomeMessage(currentCustomer, _workContext.WorkingLanguage.Id); + { + // send customer welcome message + _messageFactory.SendCustomerWelcomeMessage(currentCustomer, _workContext.WorkingLanguage.Id); - //result - return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredStandard); - } + return new AuthorizationResult(OpenAuthenticationStatus.AutoRegisteredStandard); + } default: break; } @@ -209,11 +205,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/Authentication/FormsAuthenticationService.cs b/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs index 4f88412b3f..3e5a33fa85 100644 --- a/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs +++ b/src/Libraries/SmartStore.Services/Authentication/FormsAuthenticationService.cs @@ -18,36 +18,41 @@ public partial class FormsAuthenticationService : IAuthenticationService public FormsAuthenticationService(HttpContextBase httpContext, ICustomerService customerService, CustomerSettings customerSettings) { - this._httpContext = httpContext; - this._customerService = customerService; - this._customerSettings = customerSettings; - this._expirationTimeSpan = FormsAuthentication.Timeout; + _httpContext = httpContext; + _customerService = customerService; + _customerSettings = customerSettings; + _expirationTimeSpan = FormsAuthentication.Timeout; } - public virtual void SignIn(Customer customer, bool createPersistentCookie) { var now = DateTime.UtcNow.ToLocalTime(); + var name = _customerSettings.CustomerLoginType != CustomerLoginType.Email ? customer.Username : customer.Email; + - var ticket = new FormsAuthenticationTicket( + var ticket = new FormsAuthenticationTicket( 1 /*version*/, - _customerSettings.UsernamesEnabled ? customer.Username : customer.Email, + name, now, now.Add(_expirationTimeSpan), createPersistentCookie, - _customerSettings.UsernamesEnabled ? customer.Username : customer.Email, + name, FormsAuthentication.FormsCookiePath); var encryptedTicket = FormsAuthentication.Encrypt(ticket); - var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); - cookie.HttpOnly = true; - if (ticket.IsPersistent) + var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) + { + HttpOnly = true, + Secure = FormsAuthentication.RequireSSL, + Path = FormsAuthentication.FormsCookiePath + }; + + if (ticket.IsPersistent) { cookie.Expires = ticket.Expiration; } - cookie.Secure = FormsAuthentication.RequireSSL; - cookie.Path = FormsAuthentication.FormsCookiePath; + if (FormsAuthentication.CookieDomain != null) { cookie.Domain = FormsAuthentication.CookieDomain; @@ -72,16 +77,15 @@ public virtual Customer GetAuthenticatedCustomer() return null; Customer customer = null; - FormsIdentity formsIdentity = null; - SmartStoreIdentity smartNetIdentity = null; + SmartStoreIdentity ident = null; - if ((formsIdentity = _httpContext.User.Identity as FormsIdentity) != null) + if (_httpContext.User.Identity is FormsIdentity formsIdentity) { customer = GetAuthenticatedCustomerFromTicket(formsIdentity.Ticket); } - else if ((smartNetIdentity = _httpContext.User.Identity as SmartStoreIdentity) != null) + else if ((ident = _httpContext.User.Identity as SmartStoreIdentity) != null) { - customer = _customerService.GetCustomerById(smartNetIdentity.CustomerId); + customer = _customerService.GetCustomerById(ident.CustomerId); } if (customer != null && customer.Active && !customer.Deleted && customer.IsRegistered()) @@ -94,17 +98,27 @@ public virtual Customer GetAuthenticatedCustomer() public virtual Customer GetAuthenticatedCustomerFromTicket(FormsAuthenticationTicket ticket) { - if (ticket == null) - throw new ArgumentNullException("ticket"); + Guard.NotNull(ticket, nameof(ticket)); var usernameOrEmail = ticket.UserData; - if (String.IsNullOrWhiteSpace(usernameOrEmail)) + if (string.IsNullOrWhiteSpace(usernameOrEmail)) return null; - var customer = _customerSettings.UsernamesEnabled - ? _customerService.GetCustomerByUsername(usernameOrEmail) - : _customerService.GetCustomerByEmail(usernameOrEmail); + Customer customer = null; + + if (_customerSettings.CustomerLoginType == CustomerLoginType.Email) + { + customer = _customerService.GetCustomerByEmail(usernameOrEmail); + } + else if (_customerSettings.CustomerLoginType == CustomerLoginType.Username) + { + customer = _customerService.GetCustomerByUsername(usernameOrEmail); + } + else + { + customer = _customerService.GetCustomerByEmail(usernameOrEmail) ?? _customerService.GetCustomerByUsername(usernameOrEmail); + } return customer; } diff --git a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs index ea9c74c547..7cdfafa8b7 100644 --- a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs @@ -163,10 +163,10 @@ public virtual IPagedList GetAllBlogPostsByTag( foreach (var blogPost in blogPostsAll) { - var tags = blogPost.ParseTags().Select(x => SeoHelper.GetSeName(x, _seoSettings.ConvertNonWesternChars, _seoSettings.AllowUnicodeCharsInUrls, + true, _seoSettings.SeoNameCharConversion)); if (!String.IsNullOrEmpty(tags.FirstOrDefault(t => t.Equals(tag, StringComparison.InvariantCultureIgnoreCase)))) 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..f0c104405b 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; @@ -27,11 +28,11 @@ public partial class CategoryService : ICategoryService internal const string CATEGORY_TREE_KEY = "category:tree-{0}-{1}-{2}"; internal const string CATEGORY_TREE_PATTERN_KEY = "category:tree-*"; - private const string CATEGORIES_BY_PARENT_CATEGORY_ID_KEY = "category.byparent-{0}-{1}-{2}-{3}"; - private const string PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY = "productcategory.allbycategoryid-{0}-{1}-{2}-{3}-{4}-{5}"; - private const string PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY = "productcategory.allbyproductid-{0}-{1}-{2}-{3}"; - private const string CATEGORIES_PATTERN_KEY = "category.*"; - private const string PRODUCTCATEGORIES_PATTERN_KEY = "productcategory.*"; + private const string CATEGORIES_BY_PARENT_CATEGORY_ID_KEY = "category:byparent-{0}-{1}-{2}-{3}"; + private const string PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY = "productcategory:allbycategoryid-{0}-{1}-{2}-{3}-{4}-{5}"; + private const string PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY = "productcategory:allbyproductid-{0}-{1}-{2}-{3}"; + private const string CATEGORIES_PATTERN_KEY = "category:*"; + private const string PRODUCTCATEGORIES_PATTERN_KEY = "productcategory:*"; private readonly IRepository _categoryRepository; private readonly IRepository _productCategoryRepository; @@ -115,7 +116,7 @@ public virtual void InheritAclIntoChildren( var category = GetCategoryById(categoryId); var subcategories = GetAllCategoriesByParentCategoryId(categoryId, true); var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var categoryCustomerRoles = _aclService.GetCustomerRoleIdsWithAccess(category); + var categoryCustomerRoles = _aclService.GetCustomerRoleIdsWithAccessTo(category); var categoryIds = new HashSet(subcategories.Select(x => x.Id)); categoryIds.Add(categoryId); @@ -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); } @@ -183,12 +183,11 @@ public virtual void InheritAclIntoChildren( } else { - AclRecord aclRecordToDelete; - if (existingAclRecords.TryGetValue(customerRole.Id, out aclRecordToDelete)) - { - _aclRepository.Delete(aclRecordToDelete); - } - } + if (existingAclRecords.TryGetValue(customerRole.Id, out var aclRecordToDelete)) + { + _aclRepository.Delete(aclRecordToDelete); + } + } } } @@ -241,12 +240,11 @@ public virtual void InheritStoresIntoChildren( } else { - StoreMapping storeMappingToDelete; - if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) - { - _storeMappingRepository.Delete(storeMappingToDelete); - } - } + if (existingStoreMappingsRecords.TryGetValue(store.Id, out var storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } } } @@ -273,12 +271,11 @@ public virtual void InheritStoresIntoChildren( } else { - StoreMapping storeMappingToDelete; - if (existingStoreMappingsRecords.TryGetValue(store.Id, out storeMappingToDelete)) - { - _storeMappingRepository.Delete(storeMappingToDelete); - } - } + if (existingStoreMappingsRecords.TryGetValue(store.Id, out var storeMappingToDelete)) + { + _storeMappingRepository.Delete(storeMappingToDelete); + } + } } } @@ -314,27 +311,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 +354,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; } @@ -448,6 +430,23 @@ public virtual Category GetCategoryById(int categoryId) return _categoryRepository.GetByIdCached(categoryId, "db.category.id-" + categoryId); } + public virtual IList GetCategoriesByIds(int[] categoryIds) + { + if (categoryIds == null || !categoryIds.Any()) + { + return new List(); + } + + var query = from c in _categoryRepository.Table + where categoryIds.Contains(c.Id) + select c; + + var categories = query.ToList(); + + // Sort by passed identifier sequence. + return categories.OrderBySequence(categoryIds).ToList(); + } + public virtual void InsertCategory(Category category) { Guard.NotNull(category, nameof(category)); @@ -537,7 +536,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 +544,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) @@ -570,13 +571,20 @@ public virtual Multimap GetProductCategoriesByProductIds(i { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + 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 +711,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 +729,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 +739,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) @@ -775,6 +774,7 @@ public TreeNode GetCategoryTree(int rootCategoryId = 0, bool incl x.Id, x.ParentCategoryId, x.Name, + x.ExternalLink, x.Alias, x.PictureId, x.Published, @@ -791,6 +791,7 @@ public TreeNode GetCategoryTree(int rootCategoryId = 0, bool incl Id = x.Id, ParentCategoryId = x.ParentCategoryId, Name = x.Name, + ExternalLink = x.ExternalLink, Alias = x.Alias, PictureId = x.PictureId, Published = x.Published, diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryTreeChangeHandler.cs index 22b31b1c5c..804584a665 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,8 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; -using SmartStore.Data; +using SmartStore.Core.Events; +using SmartStore.Core.Search; namespace SmartStore.Services.Catalog { @@ -35,7 +35,7 @@ public CategoryTreeChangedEvent(CategoryTreeChangeReason reason) public CategoryTreeChangeReason Reason { get; private set; } } - public class CategoryTreeChangeHook : IDbSaveHook + public class CategoryTreeChangeHook : IDbSaveHook, IConsumer { private readonly ICommonServices _services; private readonly ICategoryService _categoryService; @@ -43,14 +43,12 @@ 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 private static readonly string[] _a = new string[] { "LimitedToStores", "SubjectToAcl" }; // Data affecting category prop names - private static readonly string[] _d = new string[] { "Name", "Alias", "PictureId", "BadgeText", "BadgeStyle" }; + private static readonly string[] _d = new string[] { "Name", "Alias", "ExternalLink", "PictureId", "BadgeText", "BadgeStyle" }; private static readonly HashSet _candidateTypes = new HashSet { @@ -64,29 +62,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 +82,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,15 +98,13 @@ 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))) { - // Hierarchy affecting properties has changed. Nuke every tree. + // Hierarchy affecting properties has changed. Nuke each tree. cache.RemoveByPattern(CategoryService.CATEGORY_TREE_PATTERN_KEY); PublishEvent(CategoryTreeChangeReason.Hierarchy); _invalidated = true; @@ -171,6 +145,7 @@ public void OnBeforeSave(IHookedEntity entry) else { value.Name = category.Name; + value.ExternalLink = category.ExternalLink; value.Alias = category.Alias; value.PictureId = category.PictureId; value.BadgeText = category.BadgeText; @@ -227,6 +202,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) @@ -283,6 +259,14 @@ public void OnAfterSave(IHookedEntity entry) } } + public void HandleEvent(IndexingCompletedEvent message) + { + if (message.IndexInfo.IsModified) + { + PublishEvent(CategoryTreeChangeReason.ElementCounts); + } + } + private void PublishEvent(CategoryTreeChangeReason reason) { if (_handledReasons[(int)reason] == false) diff --git a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs index 99a7eb444f..6368d7dae5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CompareProductsService.cs @@ -7,10 +7,10 @@ namespace SmartStore.Services.Catalog { - /// - /// Compare products service - /// - public partial class CompareProductsService : ICompareProductsService + /// + /// Compare products service + /// + public partial class CompareProductsService : ICompareProductsService { private const string COMPARE_PRODUCTS_COOKIE_NAME = "sm.CompareProducts"; @@ -34,23 +34,22 @@ public CompareProductsService( /// Gets a "compare products" identifier list /// /// "compare products" identifier list - protected List GetComparedProductIds() + protected virtual HashSet GetComparedProductIds() { - var productIds = new List(); - HttpCookie compareCookie = _httpContext.Request.Cookies.Get(COMPARE_PRODUCTS_COOKIE_NAME); + var compareCookie = _httpContext.Request.Cookies.Get(COMPARE_PRODUCTS_COOKIE_NAME); if ((compareCookie == null) || (compareCookie.Values == null)) - return productIds; - string[] values = compareCookie.Values.GetValues("CompareProductIds"); + { + return new HashSet(); + } + + var values = compareCookie.Values.GetValues("CompareProductIds"); if (values == null) - return productIds; - foreach (string productId in values) { - int prodId = int.Parse(productId); - if (!productIds.Contains(prodId)) - productIds.Add(prodId); + return new HashSet(); } - return productIds; + var result = new HashSet(values.Select(x => x.ToInt())); + return result; } #endregion @@ -77,15 +76,11 @@ public virtual void ClearCompareProducts() /// "Compare products" list public virtual IList GetComparedProducts() { - var products = new List(); var productIds = GetComparedProductIds(); - foreach (int productId in productIds) - { - var product = _productService.GetProductById(productId); - if (product != null && product.Published && !product.Deleted) - products.Add(product); - } - return products; + var products = _productService.GetProductsByIds(productIds.ToArray()); + var result = products.Where(x => !x.Deleted && x.Published && !x.IsSystemProduct).ToList(); + + return result; } public virtual int GetComparedProductsCount() diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index c3d587f42c..15088a1c1f 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,43 +456,43 @@ 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); } } } private void ProcessAssociatedProducts(Product product, Product clone, bool isPublished, bool copyImages) { - var copyOf = T("Admin.Common.CopyOf"); var searchQuery = new CatalogSearchQuery().HasParentGroupedProduct(product.Id); var query = _catalogSearchService.PrepareQuery(searchQuery); @@ -477,7 +500,7 @@ private void ProcessAssociatedProducts(Product product, Product clone, bool isPu foreach (var associatedProduct in associatedProducts) { - var associatedProductCopy = CopyProduct(associatedProduct, $"{copyOf} {associatedProduct.Name}", isPublished, copyImages, false); + var associatedProductCopy = CopyProduct(associatedProduct, T("Admin.Common.CopyOf", associatedProduct.Name), isPublished, copyImages, false); associatedProductCopy.ParentGroupedProductId = clone.Id; } } @@ -567,7 +590,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..b52589cb5e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs @@ -100,6 +100,13 @@ IPagedList GetAllCategories( /// Category Category GetCategoryById(int categoryId); + /// + /// Gets categories by Ids + /// + /// Array of category identifiers + /// List of Categories + IList GetCategoriesByIds(int[] categoryIds); + /// /// Inserts category /// @@ -194,13 +201,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 +255,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/IManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs index 8843f4eb94..f8127a3d2c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs @@ -60,6 +60,13 @@ IPagedList GetAllManufacturers(string manufacturerName, /// Manufacturer Manufacturer GetManufacturerById(int manufacturerId); + /// + /// Gets manufacturers by Ids + /// + /// Array of manufacturer identifiers + /// List of manufacturers + IList GetManufacturersByIds(int[] manufacturerIds); + /// /// Inserts a manufacturer /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 08a81d9780..0017183f46 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -15,7 +15,8 @@ public partial interface IProductAttributeParser /// Parses, prefetches & caches all passed attribute definitions for the current request /// /// All attribute definitions to prefetch - void PrefetchProductVariantAttributes(IEnumerable attributesXml); + /// Number of fetched attributes + int PrefetchProductVariantAttributes(IEnumerable attributesXml); /// /// Gets selected product variant attributes as a map of integer ids with their corresponding values. @@ -38,13 +39,18 @@ public partial interface IProductAttributeParser /// Product variant attribute values IEnumerable ParseProductVariantAttributeValues(string attributesXml); - /// - /// Get list of product variant attribute values - /// - /// Map of combined attributes - /// Product variant attributes - /// Collection of product variant attribute values - ICollection ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes); + /// + /// Clear cached product variant attribute values. + /// + void ClearCachedAttributeValues(); + + /// + /// Get list of product variant attribute values + /// + /// Map of combined attributes + /// Product variant attributes + /// Collection of product variant attribute values + ICollection ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes); /// /// Gets selected product variant attribute value diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index 03ccc886e2..7c02afc179 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 @@ -276,12 +283,26 @@ public partial interface IProductAttributeService ProductVariantAttributeCombination GetProductVariantAttributeCombinationById(int productVariantAttributeCombinationId); /// - /// /// Gets a product variant attribute combination by SKU + /// Gets a product variant attribute combination by SKU /// /// SKU /// Product variant attribute combination ProductVariantAttributeCombination GetProductVariantAttributeCombinationBySku(string sku); + /// + /// Gets a product variant attribute combination by GTIN. + /// + /// GTIN. + /// Product variant attribute combination. + ProductVariantAttributeCombination GetAttributeCombinationByGtin(string gtin); + + /// + /// Gets a product variant attribute combination by manufacturer part number. + /// + /// Manufacturer part number. + /// Product variant attribute combination. + ProductVariantAttributeCombination GetAttributeCombinationByMpn(string manufacturerPartNumber); + /// /// Inserts a product variant attribute combination /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index 739b683e7b..89cc3363ae 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 @@ -146,12 +153,20 @@ public partial interface IProductService /// Map of product tags Multimap GetProductTagsByProductIds(int[] productIds); - /// - /// Get applied discounts by product identifiers - /// - /// Product identifiers - /// Map of applied discounts - Multimap GetAppliedDiscountsByProductIds(int[] productIds); + /// + /// Gets products that are assigned to group products. + /// + /// Grouped product identifiers. + /// A value indicating whether to show hidden records. + /// Map of associated products. + Multimap GetAssociatedProductsByProductIds(int[] productIds, bool showHidden = false); + + /// + /// Get applied discounts by product identifiers + /// + /// Product identifiers + /// Map of applied discounts + Multimap GetAppliedDiscountsByProductIds(int[] productIds); #endregion @@ -384,7 +399,14 @@ public partial interface IProductService /// Map of bundle items Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false); - #endregion + /// + /// Checks whether a product is a bundle item. + /// + /// Product identifier. + /// True if the product is a bundle item. + bool IsBundleItem(int productId); + + #endregion } [Flags] diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 504c4d91a2..14daae3178 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Linq.Expressions; @@ -9,19 +8,18 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Media; -using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.DataExchange.Import.Events; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Utilities; namespace SmartStore.Services.Catalog.Importer { - public class CategoryImporter : EntityImporterBase + public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; private readonly IRepository _pictureRepository; - private readonly ICommonServices _services; private readonly ICategoryTemplateService _categoryTemplateService; private readonly IPictureService _pictureService; private readonly ILocalizedEntityService _localizedEntityService; @@ -41,7 +39,6 @@ public class CategoryImporter : EntityImporterBase public CategoryImporter( IRepository categoryRepository, IRepository pictureRepository, - ICommonServices services, ICategoryTemplateService categoryTemplateService, IPictureService pictureService, ILocalizedEntityService localizedEntityService, @@ -49,7 +46,6 @@ public CategoryImporter( { _categoryRepository = categoryRepository; _pictureRepository = pictureRepository; - _services = services; _categoryTemplateService = categoryTemplateService; _pictureService = pictureService; _localizedEntityService = localizedEntityService; @@ -81,20 +77,20 @@ protected override void Import(ImportExecuteContext context) { ProcessCategories(context, batch, templateViewPaths, srcToDestId); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessCategories"); } - // reduce batch to saved (valid) products. + // Reduce batch to saved (valid) products. // No need to perform import operations on errored products. batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - // update result object + // Update result object. context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - // process slugs + // Process slugs. if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) { try @@ -102,9 +98,9 @@ protected override void Import(ImportExecuteContext context) _categoryRepository.Context.AutoDetectChangesEnabled = true; ProcessSlugs(context, batch, typeof(Category).Name); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessSlugs"); } finally { @@ -112,49 +108,51 @@ protected override void Import(ImportExecuteContext context) } } - // process store mappings + // Process store mappings. if (segmenter.HasColumn("StoreIds")) { try { ProcessStoreMappings(context, batch); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessStoreMappings"); } } - // localizations + // Localizations. try { ProcessLocalizations(context, batch, _localizableProperties); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessLocalizedProperties"); } - // process pictures - if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) + // Process pictures. + if (segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) { try { _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessPictures(context, batch, srcToDestId); + ProcessPictures(context, batch); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessPictures"); } finally { _categoryRepository.Context.AutoDetectChangesEnabled = false; } } - } - // map parent id of inserted categories + context.Services.EventPublisher.Publish(new ImportBatchExecutedEvent(context, batch)); + } + + // Map parent id of inserted categories. if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) { segmenter.Reset(); @@ -168,9 +166,9 @@ protected override void Import(ImportExecuteContext context) { ProcessParentMappings(context, batch, srcToDestId); } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessParentMappings"); } } } @@ -179,83 +177,74 @@ protected override void Import(ImportExecuteContext context) protected virtual int ProcessPictures( ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) + IEnumerable> batch) { - var equalPictureId = 0; - foreach (var row in batch) { try { var srcId = row.GetDataValue("Id"); - var urlOrPath = row.GetDataValue("ImageUrl"); - - if (srcId != 0 && srcToDestId.ContainsKey(srcId) && urlOrPath.HasValue()) - { - var currentPictures = new List(); - var category = _categoryRepository.GetById(srcToDestId[srcId].DestinationId); - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - var image = CreateDownloadImage(urlOrPath, seoName, 1); - - if (category != null && image != null) - { - if (image.Url.HasValue() && !image.Success.HasValue) - { - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); - } - - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var pictureId = category.PictureId ?? 0; - if (pictureId != 0) - { - var picture = _pictureRepository.TableUntracked.Expand(x => x.MediaStorage).FirstOrDefault(x => x.Id == pictureId); - if (picture != null) - { - currentPictures.Add(picture); - } - } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (picture != null) - { - category.PictureId = picture.Id; - _categoryRepository.Update(category); - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls"); - } - } - } - else if (image.Url.HasValue()) - { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls"); - } - } - } + var imageUrl = row.GetDataValue("ImageUrl"); + if (imageUrl.IsEmpty()) + { + continue; + } + + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var image = CreateDownloadImage(context, imageUrl, seoName, 1); + + if (image.Url.HasValue() && !image.Success.HasValue) + { + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); + } + + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentPictures = new List(); + var pictureId = row.Entity.PictureId ?? 0; + if (pictureId != 0) + { + var picture = _pictureRepository.TableUntracked.Expand(x => x.MediaStorage).FirstOrDefault(x => x.Id == pictureId); + if (picture != null) + { + currentPictures.Add(picture); + } + } + + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out var equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + if (picture != null) + { + row.Entity.PictureId = picture.Id; + _categoryRepository.Update(row.Entity); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls"); + } + } + } + else if (image.Url.HasValue()) + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls"); + } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls"); + context.Result.AddWarning(ex.ToAllMessages(), row.GetRowInfo(), "ImageUrls"); } } var num = _categoryRepository.Context.SaveChanges(); - return num; } @@ -340,8 +329,9 @@ protected virtual int ProcessCategories( _categoryRepository.AutoCommitEnabled = true; var defaultTemplateId = templateViewPaths["CategoryTemplate.ProductsInGridOrLines"]; + var hasNameColumn = context.DataSegmenter.HasColumn("Name"); - foreach (var row in batch) + foreach (var row in batch) { Category category = null; var id = row.GetDataValue("Id"); @@ -373,9 +363,9 @@ protected virtual int ProcessCategories( continue; } - // a Name is required with new categories - if (!row.Segmenter.HasColumn("Name")) - { + // A name is required for new categories. + if (!row.HasDataValue("Name")) + { ++context.Result.SkippedRecords; context.Result.AddError("The 'Name' field is required for new categories. Skipping row.", row.GetRowInfo(), "Name"); continue; @@ -386,7 +376,7 @@ protected virtual int ProcessCategories( row.Initialize(category, name ?? category.Name); - if (!row.IsNew && !category.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) + if (!row.IsNew && hasNameColumn && !category.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { // Perf: use this later for SeName updates. row.NameChanged = true; @@ -433,16 +423,17 @@ protected virtual int ProcessCategories( } } - // commit whole batch at once + // Commit whole batch at once. var num = _categoryRepository.Context.SaveChanges(); - // get new category ids + // Get new category ids. foreach (var row in batch) { var id = row.GetDataValue("Id"); - - if (id != 0 && srcToDestId.ContainsKey(id)) - srcToDestId[id].DestinationId = row.Entity.Id; + if (id != 0 && srcToDestId.ContainsKey(id)) + { + srcToDestId[id].DestinationId = row.Entity.Id; + } } return num; diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 68846923bf..ed77e3abc3 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Data.Entity; -using System.Drawing; using System.Globalization; using System.IO; using System.Linq; @@ -14,244 +12,599 @@ using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Data.Utilities; using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.DataExchange.Import.Events; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Utilities; namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase - { - private readonly IRepository _productPictureRepository; - private readonly IRepository _productManufacturerRepository; - private readonly IRepository _productCategoryRepository; - private readonly IRepository _productRepository; - private readonly ICommonServices _services; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly IPictureService _pictureService; - private readonly IManufacturerService _manufacturerService; - private readonly ICategoryService _categoryService; - private readonly IProductService _productService; - private readonly IProductTemplateService _productTemplateService; - private readonly FileDownloadManager _fileDownloadManager; - - private static readonly Dictionary>> _localizableProperties = new Dictionary>> - { - { "Name", x => x.Name }, - { "ShortDescription", x => x.ShortDescription }, - { "FullDescription", x => x.FullDescription }, - { "MetaKeywords", x => x.MetaKeywords }, - { "MetaDescription", x => x.MetaDescription }, - { "MetaTitle", x => x.MetaTitle }, - { "BundleTitleText", x => x.BundleTitleText } - }; - - public ProductImporter( - IRepository productPictureRepository, - IRepository productManufacturerRepository, - IRepository productCategoryRepository, - IRepository productRepository, - ICommonServices services, - ILocalizedEntityService localizedEntityService, - IPictureService pictureService, - IManufacturerService manufacturerService, - ICategoryService categoryService, - IProductService productService, - IProductTemplateService productTemplateService, - FileDownloadManager fileDownloadManager) - { - _productPictureRepository = productPictureRepository; - _productManufacturerRepository = productManufacturerRepository; - _productCategoryRepository = productCategoryRepository; - _productRepository = productRepository; - _services = services; - _localizedEntityService = localizedEntityService; - _pictureService = pictureService; - _manufacturerService = manufacturerService; - _categoryService = categoryService; - _productService = productService; - _productTemplateService = productTemplateService; - _fileDownloadManager = fileDownloadManager; - } - - protected override void Import(ImportExecuteContext context) - { - var srcToDestId = new Dictionary(); - var importStartTime = DateTime.UtcNow; - var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - - using (var scope = new DbContextScope(ctx: _productRepository.Context, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) - { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach entities - _productRepository.Context.DetachEntities(x => - { - return x is Product || x is UrlRecord || x is StoreMapping || x is ProductVariantAttribute || x is LocalizedProperty || - x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || x is Category || x is Manufacturer || - x is ProductPicture || x is Picture || x is ProductTag || x is TierPrice; - }); - //_productRepository.Context.DetachAll(true); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - // =========================================================================== - // 1.) Import products - // =========================================================================== - int savedProducts = 0; - try - { - savedProducts = ProcessProducts(context, batch, templateViewPaths, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); - } - - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew); - context.Result.ModifiedRecords += Math.Max(0, savedProducts - context.Result.NewRecords); - - // =========================================================================== - // 2.) Import SEO Slugs - // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, - // as Slugs are going to be validated against existing ones in DB. - // =========================================================================== - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Product).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _productRepository.Context.AutoDetectChangesEnabled = false; - } - } - - // =========================================================================== - // 3.) Import StoreMappings - // =========================================================================== - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } - - // =========================================================================== - // 4.) Import Localizations - // =========================================================================== - try - { - ProcessLocalizations(context, batch, _localizableProperties); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); - } - - // =========================================================================== - // 5.) Import product category mappings - // =========================================================================== - if (segmenter.HasColumn("CategoryIds")) - { - try - { - ProcessProductCategories(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); - } - } - - // =========================================================================== - // 6.) Import product manufacturer mappings - // =========================================================================== - if (segmenter.HasColumn("ManufacturerIds")) - { - try - { - ProcessProductManufacturers(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); - } - } - - // =========================================================================== - // 7.) Import product picture mappings - // =========================================================================== - if (segmenter.HasColumn("ImageUrls")) - { - try - { - ProcessProductPictures(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); - } - } - } - - // =========================================================================== - // 8.) Map parent id of inserted products - // =========================================================================== - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) - { - segmenter.Reset(); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - - _productRepository.Context.DetachAll(false); - - try - { - ProcessProductMappings(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); - } - } - } - - // =========================================================================== - // 9.) PostProcess: normalization - // =========================================================================== - DataMigrator.FixProductMainPictureIds(_productRepository.Context, importStartTime); - } - } - - protected virtual int ProcessProducts( + public class ProductImporter : EntityImporterBase + { + private readonly IRepository _productPictureRepository; + private readonly IRepository _productManufacturerRepository; + private readonly IRepository _productCategoryRepository; + private readonly IRepository _productTagRepository; + private readonly IRepository _productRepository; + private readonly IRepository _tierPriceRepository; + private readonly IRepository _attributeValueRepository; + private readonly IRepository _attributeCombinationRepository; + private readonly ILocalizedEntityService _localizedEntityService; + private readonly IPictureService _pictureService; + private readonly IManufacturerService _manufacturerService; + private readonly ICategoryService _categoryService; + private readonly IProductService _productService; + private readonly IProductTemplateService _productTemplateService; + private readonly IProductAttributeService _productAttributeService; + private readonly FileDownloadManager _fileDownloadManager; + + private static readonly Dictionary>> _localizableProperties = new Dictionary>> + { + { "Name", x => x.Name }, + { "ShortDescription", x => x.ShortDescription }, + { "FullDescription", x => x.FullDescription }, + { "MetaKeywords", x => x.MetaKeywords }, + { "MetaDescription", x => x.MetaDescription }, + { "MetaTitle", x => x.MetaTitle }, + { "BundleTitleText", x => x.BundleTitleText } + }; + + public ProductImporter( + IRepository productPictureRepository, + IRepository productManufacturerRepository, + IRepository productCategoryRepository, + IRepository productTagRepository, + IRepository productRepository, + IRepository tierPriceRepository, + IRepository attributeValueRepository, + IRepository attributeCombinationRepository, + ILocalizedEntityService localizedEntityService, + IPictureService pictureService, + IManufacturerService manufacturerService, + ICategoryService categoryService, + IProductService productService, + IProductTemplateService productTemplateService, + IProductAttributeService productAttributeService, + FileDownloadManager fileDownloadManager) + { + _productPictureRepository = productPictureRepository; + _productManufacturerRepository = productManufacturerRepository; + _productCategoryRepository = productCategoryRepository; + _productTagRepository = productTagRepository; + _productRepository = productRepository; + _tierPriceRepository = tierPriceRepository; + _attributeValueRepository = attributeValueRepository; + _attributeCombinationRepository = attributeCombinationRepository; + _localizedEntityService = localizedEntityService; + _pictureService = pictureService; + _manufacturerService = manufacturerService; + _categoryService = categoryService; + _productService = productService; + _productTemplateService = productTemplateService; + _productAttributeService = productAttributeService; + _fileDownloadManager = fileDownloadManager; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + protected override void Import(ImportExecuteContext context) + { + using (var scope = new DbContextScope(ctx: context.Services.DbContext, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + { + Initialize(context); + + if (context.File.RelatedType.HasValue) + { + switch (context.File.RelatedType.Value) + { + case RelatedEntityType.TierPrice: + ImportTierPrices(context); + break; + case RelatedEntityType.ProductVariantAttributeValue: + ImportAttributeValues(context); + break; + case RelatedEntityType.ProductVariantAttributeCombination: + ImportAttributeCombinations(context); + break; + } + } + else + { + ImportProducts(context); + } + } + } + + protected virtual void ImportProducts(ImportExecuteContext context) + { + var segmenter = context.DataSegmenter; + var srcToDestId = new Dictionary(); + var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach entities + _productRepository.Context.DetachEntities(x => + { + return x is Product || x is UrlRecord || x is StoreMapping || x is ProductVariantAttribute || x is LocalizedProperty || + x is ProductBundleItem || x is ProductCategory || x is ProductManufacturer || x is Category || x is Manufacturer || + x is ProductPicture || x is Picture || x is ProductTag || x is TierPrice; + }); + //_productRepository.Context.DetachAll(true); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + // =========================================================================== + // 1.) Import products + // =========================================================================== + int savedProducts = 0; + try + { + savedProducts = ProcessProducts(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProducts"); + } + + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew); + context.Result.ModifiedRecords += Math.Max(0, savedProducts - context.Result.NewRecords); + + // =========================================================================== + // 2.) Import SEO Slugs + // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, + // as Slugs are going to be validated against existing ones in DB. + // =========================================================================== + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try + { + _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Product).Name); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _productRepository.Context.AutoDetectChangesEnabled = false; + } + } + + // =========================================================================== + // 3.) Import StoreMappings + // =========================================================================== + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + } + + // =========================================================================== + // 4.) Import Localizations + // =========================================================================== + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessLocalizations"); + } + + // =========================================================================== + // 5.) Import product category mappings + // =========================================================================== + if (segmenter.HasColumn("CategoryIds")) + { + try + { + ProcessProductCategories(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductCategories"); + } + } + + // =========================================================================== + // 6.) Import product manufacturer mappings + // =========================================================================== + if (segmenter.HasColumn("ManufacturerIds")) + { + try + { + ProcessProductManufacturers(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductManufacturers"); + } + } + + // =========================================================================== + // 7.) Import product picture mappings + // =========================================================================== + if (segmenter.HasColumn("ImageUrls")) + { + try + { + ProcessProductPictures(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductPictures"); + } + } + + // =========================================================================== + // 8.) Import product tag names + // =========================================================================== + if (segmenter.HasColumn("TagNames")) + { + try + { + ProcessProductTags(context, batch); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessProductTags"); + } + } + + context.Services.EventPublisher.Publish(new ImportBatchExecutedEvent(context, batch)); + } + + // =========================================================================== + // 9.) Map parent id of inserted products + // =========================================================================== + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) + { + segmenter.Reset(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); + + _productRepository.Context.DetachAll(false); + + try + { + ProcessProductMappings(context, batch, srcToDestId); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ProcessParentMappings"); + } + } + } + + // =========================================================================== + // 10.) PostProcess: normalization + // =========================================================================== + DataMigrator.FixProductMainPictureIds(_productRepository.Context, UtcNow); + } + + protected virtual void ImportTierPrices(ImportExecuteContext context) + { + var segmenter = context.DataSegmenter; + var entityName = RelatedEntityType.TierPrice.GetLocalizedEnum(context.Services.Localization, context.Services.WorkContext); + var processingInfo = T("Admin.Common.ProcessingInfo").Text; + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var savedEntities = 0; + var batch = segmenter.GetCurrentBatch(); + var msg = processingInfo.FormatInvariant(entityName, segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + _tierPriceRepository.Context.DetachEntities(x => x is TierPrice || x is Product); + + context.SetProgress(msg); + + try + { + _tierPriceRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var tierPrice = id > 0 ? _tierPriceRepository.GetById(id) : null; + + if (tierPrice == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + // ProductId is required for new tier prices. + var productId = row.GetDataValue("ProductId"); + if (productId == 0) + { + ++context.Result.SkippedRecords; + context.Result.AddError("The 'ProductId' field is required for new tier prices. Skipping row.", row.GetRowInfo(), "ProductId"); + continue; + } + + tierPrice = new TierPrice + { + ProductId = productId + }; + } + + row.Initialize(tierPrice, null); + + // Ignore ProductId field. We only update volatile property values and want to avoid accidents. + row.SetProperty(context.Result, (x) => x.StoreId); + row.SetProperty(context.Result, (x) => x.CustomerRoleId); + row.SetProperty(context.Result, (x) => x.Quantity); + row.SetProperty(context.Result, (x) => x.Price); + + if (row.TryGetDataValue("CalculationMethod", out int calcMethod)) + { + tierPrice.CalculationMethod = (TierPriceCalculationMethod)calcMethod; + } + + if (row.IsTransient) + { + _tierPriceRepository.Insert(tierPrice); + } + else + { + //_tierPriceRepository.Update(tierPrice); // unnecessary: we use DetectChanges() + } + } + + savedEntities = _tierPriceRepository.Context.SaveChanges(); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ImportTierPrices"); + } + + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + context.Result.NewRecords += batch.Count(x => x.IsNew); + context.Result.ModifiedRecords += Math.Max(0, savedEntities - context.Result.NewRecords); + + // Update has tier prices property for inserted records. + var insertedProductIds = new HashSet(batch.Where(x => x.IsNew).Select(x => x.Entity.ProductId)); + var products = _productService.GetProductsByIds(insertedProductIds.ToArray()); + if (products.Any()) + { + products.Each(x => _productService.UpdateHasTierPricesProperty(x)); + _productRepository.Context.SaveChanges(); + } + } + } + + protected virtual void ImportAttributeValues(ImportExecuteContext context) + { + var segmenter = context.DataSegmenter; + var entityName = RelatedEntityType.ProductVariantAttributeValue.GetLocalizedEnum(context.Services.Localization, context.Services.WorkContext); + var processingInfo = T("Admin.Common.ProcessingInfo").Text; + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var savedEntities = 0; + var batch = segmenter.GetCurrentBatch(); + var msg = processingInfo.FormatInvariant(entityName, segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + _attributeValueRepository.Context.DetachEntities(x => x is ProductVariantAttributeValue); + + context.SetProgress(msg); + + try + { + _attributeValueRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var attributeValue = id > 0 ? _attributeValueRepository.GetById(id) : null; + + if (attributeValue == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + // ProductVariantAttributeId is required for new attribute values. + var pvaId = row.GetDataValue("ProductVariantAttributeId"); + if (pvaId == 0) + { + ++context.Result.SkippedRecords; + context.Result.AddError("The 'ProductVariantAttributeId' field is required for new attribute values. Skipping row.", row.GetRowInfo(), "ProductVariantAttributeId"); + continue; + } + + if (!row.HasDataValue("Name")) + { + ++context.Result.SkippedRecords; + context.Result.AddError("The 'Name' field is required for new attribute values. Skipping row.", row.GetRowInfo(), "Name"); + continue; + } + + attributeValue = new ProductVariantAttributeValue + { + ProductVariantAttributeId = pvaId + }; + } + + row.Initialize(attributeValue, null); + + // Ignore ProductVariantAttributeId field. We only update volatile property values and want to avoid accidents. + row.SetProperty(context.Result, (x) => x.Alias); + row.SetProperty(context.Result, (x) => x.Name); + row.SetProperty(context.Result, (x) => x.Color); + row.SetProperty(context.Result, (x) => x.PriceAdjustment); + row.SetProperty(context.Result, (x) => x.WeightAdjustment); + row.SetProperty(context.Result, (x) => x.Quantity, 10000); + row.SetProperty(context.Result, (x) => x.IsPreSelected); + row.SetProperty(context.Result, (x) => x.DisplayOrder); + row.SetProperty(context.Result, (x) => x.ValueTypeId); + row.SetProperty(context.Result, (x) => x.LinkedProductId); + + if (row.IsTransient) + { + _attributeValueRepository.Insert(attributeValue); + } + else + { + //_attributeValueRepository.Update(attributeValue); // unnecessary: we use DetectChanges() + } + } + + savedEntities = _attributeValueRepository.Context.SaveChanges(); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ImportAttributeValues"); + } + + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + context.Result.NewRecords += batch.Count(x => x.IsNew); + context.Result.ModifiedRecords += Math.Max(0, savedEntities - context.Result.NewRecords); + + // MediaHelper.UpdatePictureTransientStateFor() not required, I guess, because there is no picture upload. + } + } + + protected virtual void ImportAttributeCombinations(ImportExecuteContext context) + { + var segmenter = context.DataSegmenter; + var entityName = RelatedEntityType.ProductVariantAttributeCombination.GetLocalizedEnum(context.Services.Localization, context.Services.WorkContext); + var processingInfo = T("Admin.Common.ProcessingInfo").Text; + var lowestCombinationPriceProductIds = new HashSet(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var savedEntities = 0; + var batch = segmenter.GetCurrentBatch(); + var msg = processingInfo.FormatInvariant(entityName, segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + _attributeCombinationRepository.Context.DetachEntities(x => x is ProductVariantAttributeCombination || x is Product); + + context.SetProgress(msg); + + try + { + _attributeCombinationRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var combination = id > 0 ? _attributeCombinationRepository.GetById(id) : null; + + // No Id? Try key fields. + if (combination == null) + { + foreach (var keyName in context.KeyFieldNames) + { + var keyValue = row.GetDataValue(keyName); + if (keyValue.HasValue()) + { + switch (keyName) + { + case "Sku": + combination = _productAttributeService.GetProductVariantAttributeCombinationBySku(keyValue); + break; + case "Gtin": + combination = _productAttributeService.GetAttributeCombinationByGtin(keyValue); + break; + case "ManufacturerPartNumber": + combination = _productAttributeService.GetAttributeCombinationByMpn(keyValue); + break; + } + } + + if (combination != null) + { + break; + } + } + } + + if (combination == null) + { + // We do not insert records here to avoid inconsistent attribute combination data. + ++context.Result.SkippedRecords; + context.Result.AddError("The 'Id' or another key field is required. Inserting attribute combinations not supported. Skipping row.", row.GetRowInfo(), "Id"); + continue; + } + + row.Initialize(combination, null); + + if (row.TryGetDataValue("Price", out decimal? price) && price != combination.Price) + { + lowestCombinationPriceProductIds.Add(combination.ProductId); + } + + // Ignore ProductId field. We only update volatile property values and want to avoid accidents. + row.SetProperty(context.Result, (x) => x.Sku); + row.SetProperty(context.Result, (x) => x.Gtin); + row.SetProperty(context.Result, (x) => x.ManufacturerPartNumber); + row.SetProperty(context.Result, (x) => x.StockQuantity, 10000); + row.SetProperty(context.Result, (x) => x.Price); + row.SetProperty(context.Result, (x) => x.Length); + row.SetProperty(context.Result, (x) => x.Width); + row.SetProperty(context.Result, (x) => x.Height); + row.SetProperty(context.Result, (x) => x.BasePriceAmount); + row.SetProperty(context.Result, (x) => x.BasePriceBaseAmount); + row.SetProperty(context.Result, (x) => x.AssignedPictureIds); + row.SetProperty(context.Result, (x) => x.IsActive, true); + row.SetProperty(context.Result, (x) => x.AllowOutOfStockOrders); + row.SetProperty(context.Result, (x) => x.DeliveryTimeId); + row.SetProperty(context.Result, (x) => x.QuantityUnitId); + row.SetProperty(context.Result, (x) => x.AttributesXml); + } + + savedEntities = _attributeCombinationRepository.Context.SaveChanges(); + } + catch (Exception ex) + { + context.Result.AddError(ex, segmenter.CurrentSegment, "ImportAttributeCombinations"); + } + + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + context.Result.NewRecords += batch.Count(x => x.IsNew); + context.Result.ModifiedRecords += Math.Max(0, savedEntities - context.Result.NewRecords); + + // Update lowest attribute combination price property. + var products = _productService.GetProductsByIds(lowestCombinationPriceProductIds.ToArray()); + if (products.Any()) + { + products.Each(x => _productService.UpdateLowestAttributeCombinationPriceProperty(x)); + _productRepository.Context.SaveChanges(); + } + } + } + + protected virtual int ProcessProducts( ImportExecuteContext context, IEnumerable> batch, Dictionary templateViewPaths, @@ -260,6 +613,7 @@ protected virtual int ProcessProducts( _productRepository.AutoCommitEnabled = false; var defaultTemplateId = templateViewPaths["Product"]; + var hasNameColumn = context.DataSegmenter.HasColumn("Name"); foreach (var row in batch) { @@ -304,7 +658,7 @@ protected virtual int ProcessProducts( continue; } - // a Name is required for new products. + // A name is required for new products. if (!row.HasDataValue("Name")) { ++context.Result.SkippedRecords; @@ -319,7 +673,7 @@ protected virtual int ProcessProducts( row.Initialize(product, name ?? product.Name); - if (!row.IsNew) + if (!row.IsNew && hasNameColumn) { if (!product.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) { @@ -354,8 +708,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 +738,8 @@ 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.HideQuantityControl); row.SetProperty(context.Result, (x) => x.AllowedQuantities); row.SetProperty(context.Result, (x) => x.DisableBuyButton); row.SetProperty(context.Result, (x) => x.DisableWishlistButton); @@ -424,10 +779,14 @@ protected virtual int ProcessProducts( row.SetProperty(context.Result, (x) => x.CustomsTariffNumber); row.SetProperty(context.Result, (x) => x.CountryOfOriginId); - string tvp; - if (row.TryGetDataValue("ProductTemplateViewPath", out tvp, row.IsTransient)) + if (row.TryGetDataValue("QuantiyControlType", out int qct)) + { + product.QuantiyControlType = (QuantityControlType)qct; + } + + if (row.TryGetDataValue("ProductTemplateViewPath", out string tvp, row.IsTransient)) { - product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + product.ProductTemplateId = tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId; } if (id != 0 && !srcToDestId.ContainsKey(id)) @@ -445,16 +804,17 @@ protected virtual int ProcessProducts( } } - // commit whole batch at once + // Commit whole batch at once. var num = _productRepository.Context.SaveChanges(); - // get new product ids + // Get new product ids. foreach (var row in batch) { var id = row.GetDataValue("Id"); - - if (id != 0 && srcToDestId.ContainsKey(id)) - srcToDestId[id].DestinationId = row.Entity.Id; + if (id != 0 && srcToDestId.ContainsKey(id)) + { + srcToDestId[id].DestinationId = row.Entity.Id; + } } return num; @@ -474,7 +834,7 @@ protected virtual int ProcessProductMappings( if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) { - // only touch relationship if child and parent were inserted + // Only touch relationship if child and parent were inserted. if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) { var product = _productRepository.GetById(srcToDestId[id].DestinationId); @@ -488,7 +848,6 @@ protected virtual int ProcessProductMappings( } var num = _productRepository.Context.SaveChanges(); - return num; } @@ -511,10 +870,10 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); var imageFiles = new List(); - // collect required image file infos + // Collect required image file infos. foreach (var urlOrPath in imageUrls) { - var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); + var image = CreateDownloadImage(context, urlOrPath, seoName, ++imageNumber); if (image != null) imageFiles.Add(image); @@ -523,16 +882,16 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu break; } - // download images + // Download images. if (imageFiles.Any(x => x.Url.HasValue())) { - // async downloading in batch processing is inefficient cause only the image processing benefits from async, + // Async downloading in batch processing is inefficient cause only the image processing benefits from async, // not the record processing itself. a per record processing may speed up the import. AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); } - // import images + // Import images. foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) { try @@ -545,7 +904,6 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu if (pictureBinary != null && pictureBinary.Length > 0) { var currentProductPictures = _productPictureRepository.TableUntracked - .Expand(x => x.Picture) .Expand(x => x.Picture.MediaStorage) .Where(x => x.ProductId == row.Entity.Id) .ToList(); @@ -556,18 +914,16 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu if (displayOrder == -1) { - displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); + displayOrder = currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0; } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (newPicture != null) + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + + if (newPicture != null) { var mapping = new ProductPicture { @@ -581,18 +937,18 @@ protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnu } else { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddInfo($"Found equal picture in data store for {image.Url}. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); } } } else if (image.Url.HasValue()) { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddInfo($"Download failed for picture {image.Url}.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddWarning(ex.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); } } } @@ -613,7 +969,7 @@ protected virtual int ProcessProductManufacturers(ImportExecuteContext context, { if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) { - // ensure that manufacturer exists + // Ensure that manufacturer exists. var manufacturer = _manufacturerService.GetManufacturerById(id); if (manufacturer != null) { @@ -629,16 +985,15 @@ protected virtual int ProcessProductManufacturers(ImportExecuteContext context, } } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "ManufacturerIds"); } } } - // commit whole batch at once + // Commit whole batch at once. var num = _productManufacturerRepository.Context.SaveChanges(); - return num; } @@ -657,7 +1012,7 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn { if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) { - // ensure that category exists + // Ensure that category exists. var category = _categoryService.GetCategoryById(id); if (category != null) { @@ -673,19 +1028,95 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn } } } - catch (Exception exception) + catch (Exception ex) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "CategoryIds"); } } } - // commit whole batch at once + // Commit whole batch at once, var num = _productCategoryRepository.Context.SaveChanges(); return num; } - private int? ZeroToNull(object value, CultureInfo culture) + protected virtual void ProcessProductTags(ImportExecuteContext context, IEnumerable> batch) + { + // true, cause product tags must be saved and assigned an id prior adding a mapping. + _productTagRepository.AutoCommitEnabled = true; + + var productIds = batch.Select(x => x.Entity.Id).ToList(); + var tagsPerBatch = _productRepository.TableUntracked + .Expand(x => x.ProductTags) + .Where(x => productIds.Contains(x.Id)) + .ToDictionary(x => x.Id, x => x.ProductTags); + + foreach (var row in batch) + { + try + { + var tagNames = row.GetDataValue>("TagNames"); + var product = row.Entity; + + if (!tagsPerBatch.TryGetValue(product.Id, out var existingTags)) + { + existingTags = new List(); + } + + if (tagNames.IsNullOrEmpty()) + { + // Remove all tags. + if (existingTags.Any()) + { + _productTagRepository.Context.LoadCollection(product, (Product x) => x.ProductTags); + product.ProductTags.Clear(); + } + } + else + { + // Remove tags. + var tagsToRemove = new List(); + foreach (var existingTag in existingTags) + { + if (!tagNames.Any(x => x.IsCaseInsensitiveEqual(existingTag.Name))) + { + tagsToRemove.Add(existingTag); + } + } + if (tagsToRemove.Any()) + { + _productTagRepository.Context.LoadCollection(product, (Product x) => x.ProductTags); + tagsToRemove.Each(x => product.ProductTags.Remove(x)); + } + + // Add tags. + foreach (var tagName in tagNames.Distinct()) + { + if (!existingTags.Any(x => x.Name.IsCaseInsensitiveEqual(tagName))) + { + var productTag = _productTagRepository.Table.FirstOrDefault(x => x.Name == tagName); + if (productTag == null) + { + productTag = new ProductTag { Name = tagName }; + _productTagRepository.Insert(productTag); + } + + product.ProductTags.Add(productTag); + } + } + } + } + catch (Exception ex) + { + context.Result.AddWarning(ex.Message, row.GetRowInfo(), "TagNames"); + } + } + + // Commit whole batch at once. + _productTagRepository.Context.SaveChanges(); + } + + private int? ZeroToNull(object value, CultureInfo culture) { int result; if (CommonHelper.TryConvert(value, culture, out result) && result > 0) @@ -696,7 +1127,7 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn return (int?)null; } - public static string[] SupportedKeyFields + public static string[] SupportedKeyFields { get { diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index 8363f604ce..ae476bb05d 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; @@ -14,10 +15,10 @@ namespace SmartStore.Services.Catalog { public partial class ManufacturerService : IManufacturerService { - private const string PRODUCTMANUFACTURERS_ALLBYMANUFACTURERID_KEY = "SmartStore.productmanufacturer.allbymanufacturerid-{0}-{1}-{2}-{3}-{4}"; - private const string PRODUCTMANUFACTURERS_ALLBYPRODUCTID_KEY = "SmartStore.productmanufacturer.allbyproductid-{0}-{1}-{2}"; - private const string MANUFACTURERS_PATTERN_KEY = "SmartStore.manufacturer.*"; - private const string PRODUCTMANUFACTURERS_PATTERN_KEY = "SmartStore.productmanufacturer.*"; + private const string PRODUCTMANUFACTURERS_ALLBYMANUFACTURERID_KEY = "productmanufacturer:allbymanufacturerid-{0}-{1}-{2}-{3}-{4}"; + private const string PRODUCTMANUFACTURERS_ALLBYPRODUCTID_KEY = "productmanufacturer:allbyproductid-{0}-{1}-{2}"; + private const string MANUFACTURERS_PATTERN_KEY = "manufacturer:*"; + private const string PRODUCTMANUFACTURERS_PATTERN_KEY = "productmanufacturer:*"; private readonly IRepository _manufacturerRepository; private readonly IRepository _productManufacturerRepository; @@ -120,6 +121,23 @@ public virtual Manufacturer GetManufacturerById(int manufacturerId) return _manufacturerRepository.GetByIdCached(manufacturerId, "db.manu.id-" + manufacturerId); } + public virtual IList GetManufacturersByIds(int[] manufacturerIds) + { + if (manufacturerIds == null || !manufacturerIds.Any()) + { + return new List(); + } + + var query = from m in _manufacturerRepository.Table + where manufacturerIds.Contains(m.Id) + select m; + + var manufacturers = query.ToList(); + + // Sort by passed identifier sequence. + return manufacturers.OrderBySequence(manufacturerIds).ToList(); + } + public virtual void InsertManufacturer(Manufacturer manufacturer) { if (manufacturer == null) @@ -173,9 +191,7 @@ 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) + where pm.ManufacturerId == manufacturerId && !p.Deleted && (showHidden || p.Published) orderby pm.DisplayOrder select pm; @@ -216,12 +232,9 @@ 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; @@ -249,7 +262,9 @@ orderby mGroup.Key query = query.OrderBy(pm => pm.DisplayOrder); } - var productManufacturers = query.ToList(); + query = query.Include(x => x.Manufacturer.Picture); + + var productManufacturers = query.ToList(); return productManufacturers; }); } @@ -273,12 +288,19 @@ public virtual Multimap GetProductManufacturersByProdu { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + 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/Modelling/ProductVariantQueryFactory.cs b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs index f115f77341..954eebf7d5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Modelling/ProductVariantQueryFactory.cs @@ -4,6 +4,7 @@ using System.Text.RegularExpressions; using System.Web; using SmartStore.Collections; +using SmartStore.Core.Html; using SmartStore.Services.Search.Modelling; namespace SmartStore.Services.Catalog.Modelling @@ -38,11 +39,10 @@ protected Multimap QueryItems { _queryItems = new Multimap(); - if (_httpContext.Request != null) + if (_httpContext.Request?.Unvalidated != null) { - var form = _httpContext.Request.Form; - var query = _httpContext.Request.QueryString; - + var form = _httpContext.Request.Unvalidated.Form; + if (form != null) { foreach (var key in form.AllKeys) @@ -54,7 +54,9 @@ protected Multimap QueryItems } } - if (query != null) + var query = _httpContext.Request.Unvalidated.QueryString; + + if (query != null) { foreach (var key in query.AllKeys) { @@ -71,7 +73,51 @@ protected Multimap QueryItems } } - private DateTime? ConvertToDate(string key, string value) + public ProductVariantQuery Current { get; private set; } + + public ProductVariantQuery CreateFromQuery() + { + var query = new ProductVariantQuery(); + Current = query; + + if (_httpContext.Request == null) + return query; + + var languageId = _services.WorkContext.WorkingLanguage.Id; + + foreach (var item in QueryItems) + { + if (!item.Value.Any() || item.Key.EndsWith("-day") || item.Key.EndsWith("-month")) + { + continue; + } + + if (IsVariantKey.IsMatch(item.Key)) + { + ConvertVariant(query, item.Key, item.Value); + } + else if (IsGiftCardKey.IsMatch(item.Key)) + { + item.Value.Each(value => ConvertGiftCard(query, item.Key, value)); + } + else if (IsCheckoutAttributeKey.IsMatch(item.Key)) + { + ConvertCheckoutAttribute(query, item.Key, item.Value); + } + else if (IsVariantAliasKey.IsMatch(item.Key)) + { + ConvertVariantAlias(query, item.Key, item.Value, languageId); + } + else + { + ConvertItems(_httpContext.Request, query, item.Key, item.Value); + } + } + + return query; + } + + private DateTime? ConvertToDate(string key, string value) { var year = 0; var month = 0; @@ -119,15 +165,17 @@ protected virtual void ConvertVariant(ProductVariantQuery query, string key, ICo if (isDate || isFile || isText) { var value = isText ? string.Join(",", values) : values.First(); - var variant = new ProductVariantQueryItem(value); - variant.ProductId = ids[0].ToInt(); - variant.BundleItemId = ids[1].ToInt(); - variant.AttributeId = ids[2].ToInt(); - variant.VariantAttributeId = ids[3].ToInt(); - variant.IsFile = isFile; - variant.IsText = isText; - - if (isDate) + var variant = new ProductVariantQueryItem(value) + { + ProductId = ids[0].ToInt(), + BundleItemId = ids[1].ToInt(), + AttributeId = ids[2].ToInt(), + VariantAttributeId = ids[3].ToInt(), + IsFile = isFile, + IsText = isText + }; + + if (isDate) { variant.Date = ConvertToDate(key, value); } @@ -138,13 +186,15 @@ protected virtual void ConvertVariant(ProductVariantQuery query, string key, ICo { foreach (var value in values) { - var variant = new ProductVariantQueryItem(value); - variant.ProductId = ids[0].ToInt(); - variant.BundleItemId = ids[1].ToInt(); - variant.AttributeId = ids[2].ToInt(); - variant.VariantAttributeId = ids[3].ToInt(); - - query.AddVariant(variant); + var variant = new ProductVariantQueryItem(value) + { + ProductId = ids[0].ToInt(), + BundleItemId = ids[1].ToInt(), + AttributeId = ids[2].ToInt(), + VariantAttributeId = ids[3].ToInt() + }; + + query.AddVariant(variant); } } } @@ -181,16 +231,18 @@ protected virtual void ConvertVariantAlias(ProductVariantQuery query, string key if (isDate || isFile || isText) { var value = isText ? string.Join(",", values) : values.First(); - var variant = new ProductVariantQueryItem(value); - variant.ProductId = productId; - variant.BundleItemId = bundleItemId; - variant.AttributeId = attributeId; - variant.VariantAttributeId = variantAttributeId; - variant.Alias = alias; - variant.IsFile = isFile; - variant.IsText = isText; - - if (isDate) + var variant = new ProductVariantQueryItem(value) + { + ProductId = productId, + BundleItemId = bundleItemId, + AttributeId = attributeId, + VariantAttributeId = variantAttributeId, + Alias = alias, + IsFile = isFile, + IsText = isText + }; + + if (isDate) { variant.Date = ConvertToDate(key, value); } @@ -213,14 +265,16 @@ protected virtual void ConvertVariantAlias(ProductVariantQuery query, string key valueAlias = string.Join("-", valueIds.Take(valueIds.Length - 1)); } - var variant = new ProductVariantQueryItem(optionId == 0 ? value : optionId.ToString()); - variant.ProductId = productId; - variant.BundleItemId = bundleItemId; - variant.AttributeId = attributeId; - variant.VariantAttributeId = variantAttributeId; - variant.Alias = alias; + var variant = new ProductVariantQueryItem(optionId == 0 ? value : optionId.ToString()) + { + ProductId = productId, + BundleItemId = bundleItemId, + AttributeId = attributeId, + VariantAttributeId = variantAttributeId, + Alias = alias + }; - if (optionId != 0) + if (optionId != 0) { variant.ValueAlias = valueAlias; } @@ -257,11 +311,13 @@ protected virtual void ConvertCheckoutAttribute(ProductVariantQuery query, strin if (isDate || isFile || isText) { var value = isText ? string.Join(",", values) : values.First(); - var attribute = new CheckoutAttributeQueryItem(attributeId, value); - attribute.IsFile = isFile; - attribute.IsText = isText; + var attribute = new CheckoutAttributeQueryItem(attributeId, value) + { + IsFile = isFile, + IsText = isText + }; - if (isDate) + if (isDate) { attribute.Date = ConvertToDate(key, value); } @@ -280,49 +336,5 @@ protected virtual void ConvertCheckoutAttribute(ProductVariantQuery query, strin protected virtual void ConvertItems(HttpRequestBase request, ProductVariantQuery query, string key, ICollection values) { } - - public ProductVariantQuery Current { get; private set; } - - public ProductVariantQuery CreateFromQuery() - { - var query = new ProductVariantQuery(); - Current = query; - - if (_httpContext.Request == null) - return query; - - var languageId = _services.WorkContext.WorkingLanguage.Id; - - foreach (var item in QueryItems) - { - if (!item.Value.Any() || item.Key.EndsWith("-day") || item.Key.EndsWith("-month")) - { - continue; - } - - if (IsVariantKey.IsMatch(item.Key)) - { - ConvertVariant(query, item.Key, item.Value); - } - else if (IsGiftCardKey.IsMatch(item.Key)) - { - item.Value.Each(value => ConvertGiftCard(query, item.Key, value)); - } - else if (IsCheckoutAttributeKey.IsMatch(item.Key)) - { - ConvertCheckoutAttribute(query, item.Key, item.Value); - } - else if (IsVariantAliasKey.IsMatch(item.Key)) - { - ConvertVariantAlias(query, item.Key, item.Value, languageId); - } - else - { - ConvertItems(_httpContext.Request, query, item.Key, item.Value); - } - } - - return query; - } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs index eb474feab4..eea8195b4f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs @@ -7,55 +7,61 @@ namespace SmartStore.Services.Catalog { - /// - /// Cargo data to reduce database round trips during price calculation - /// - public class PriceCalculationContext + /// + /// Cargo data to reduce database round trips during price calculation + /// + public class PriceCalculationContext { protected List _productIds; private List _productIdsTierPrices; private List _productIdsAppliedDiscounts; - private List _productIdsBundleItems; + private List _bundledProductIds; + private List _groupedProductIds; - private Func> _funcAttributes; + private Func> _funcAttributes; private Func> _funcAttributeCombinations; private Func> _funcTierPrices; private Func> _funcProductCategories; private Func> _funcProductManufacturers; private Func> _funcAppliedDiscounts; private Func> _funcProductBundleItems; + private Func> _funcAssociatedProducts; - private LazyMultimap _attributes; + private LazyMultimap _attributes; private LazyMultimap _attributeCombinations; private LazyMultimap _tierPrices; private LazyMultimap _productCategories; private LazyMultimap _productManufacturers; private LazyMultimap _appliedDiscounts; private LazyMultimap _productBundleItems; + private LazyMultimap _associatedProducts; - public PriceCalculationContext(IEnumerable products, + public PriceCalculationContext(IEnumerable products, Func> attributes, Func> attributeCombinations, Func> tierPrices, Func> productCategories, Func> productManufacturers, Func> appliedDiscounts, - Func> productBundleItems) + Func> productBundleItems, + Func> associatedProducts) { if (products == null) { _productIds = new List(); _productIdsTierPrices = new List(); _productIdsAppliedDiscounts = new List(); - _productIdsBundleItems = new List(); + _bundledProductIds = new List(); + _groupedProductIds = new List(); } else { _productIds = new List(products.Select(x => x.Id)); _productIdsTierPrices = new List(products.Where(x => x.HasTierPrices).Select(x => x.Id)); _productIdsAppliedDiscounts = new List(products.Where(x => x.HasDiscountsApplied).Select(x => x.Id)); - _productIdsBundleItems = new List(products.Where(x => x.ProductType == ProductType.BundledProduct).Select(x => x.Id)); - } + _bundledProductIds = new List(products.Where(x => x.ProductType == ProductType.BundledProduct).Select(x => x.Id)); + _groupedProductIds = new List(products.Where(x => x.ProductType == ProductType.GroupedProduct).Select(x => x.Id)); + } _funcAttributes = attributes; _funcAttributeCombinations = attributeCombinations; @@ -64,31 +70,24 @@ public PriceCalculationContext(IEnumerable products, _funcProductManufacturers = productManufacturers; _funcAppliedDiscounts = appliedDiscounts; _funcProductBundleItems = productBundleItems; + _funcAssociatedProducts = associatedProducts; } - public IReadOnlyList ProductIds - { - get { return _productIds; } - } + public IReadOnlyList ProductIds => _productIds; public void Clear() { - if (_attributes != null) - _attributes.Clear(); - if (_attributeCombinations != null) - _attributeCombinations.Clear(); - if (_tierPrices != null) - _tierPrices.Clear(); - if (_productCategories != null) - _productCategories.Clear(); - if (_productManufacturers != null) - _productManufacturers.Clear(); - if (_appliedDiscounts != null) - _appliedDiscounts.Clear(); - if (_productBundleItems != null) - _productBundleItems.Clear(); - - _productIdsBundleItems.Clear(); + _attributes?.Clear(); + _attributeCombinations?.Clear(); + _tierPrices?.Clear(); + _productCategories?.Clear(); + _productManufacturers?.Clear(); + _appliedDiscounts?.Clear(); + _productBundleItems?.Clear(); + _associatedProducts?.Clear(); + + _bundledProductIds.Clear(); + _groupedProductIds.Clear(); } public LazyMultimap Attributes @@ -169,13 +168,26 @@ public LazyMultimap ProductBundleItems { if (_productBundleItems == null) { - _productBundleItems = new LazyMultimap(keys => _funcProductBundleItems(keys), _productIdsBundleItems); + _productBundleItems = new LazyMultimap(keys => _funcProductBundleItems(keys), _bundledProductIds); } return _productBundleItems; } } - public void Collect(IEnumerable productIds) + public LazyMultimap AssociatedProducts + { + get + { + if (_associatedProducts == null) + { + _associatedProducts = new LazyMultimap(keys => _funcAssociatedProducts(keys), _groupedProductIds); + } + + return _associatedProducts; + } + } + + public void Collect(IEnumerable productIds) { Attributes.Collect(productIds); AttributeCombinations.Collect(productIds); @@ -183,6 +195,7 @@ public void Collect(IEnumerable productIds) ProductCategories.Collect(productIds); AppliedDiscounts.Collect(productIds); ProductBundleItems.Collect(productIds); + AssociatedProducts.Collect(productIds); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 3f6424e8a9..5fc429449c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -4,29 +4,28 @@ using System.Web; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Tax; using SmartStore.Services.Catalog.Extensions; using SmartStore.Services.Catalog.Modelling; using SmartStore.Services.Discounts; using SmartStore.Services.Media; using SmartStore.Services.Tax; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Tax; namespace SmartStore.Services.Catalog { - /// - /// Price calculation service - /// - public partial class PriceCalculationService : IPriceCalculationService + /// + /// Price calculation service + /// + public partial class PriceCalculationService : IPriceCalculationService { private readonly IDiscountService _discountService; private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; private readonly IProductAttributeParser _productAttributeParser; private readonly IProductService _productService; - private readonly ShoppingCartSettings _shoppingCartSettings; private readonly CatalogSettings _catalogSettings; private readonly IProductAttributeService _productAttributeService; private readonly IDownloadService _downloadService; @@ -41,7 +40,6 @@ public PriceCalculationService( IManufacturerService manufacturerService, IProductAttributeParser productAttributeParser, IProductService productService, - ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings, IProductAttributeService productAttributeService, IDownloadService downloadService, @@ -55,7 +53,6 @@ public PriceCalculationService( _manufacturerService = manufacturerService; _productAttributeParser = productAttributeParser; _productService = productService; - _shoppingCartSettings = shoppingCartSettings; _catalogSettings = catalogSettings; _productAttributeService = productAttributeService; _downloadService = downloadService; @@ -73,9 +70,9 @@ public PriceCalculationService( /// Product /// Customer /// Discounts - protected virtual IList GetAllowedDiscounts(Product product, Customer customer, PriceCalculationContext context = null) + protected virtual ICollection GetAllowedDiscounts(Product product, Customer customer, PriceCalculationContext context = null) { - var result = new List(); + var result = new HashSet(); if (_catalogSettings.IgnoreDiscounts) return result; @@ -102,7 +99,7 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer { foreach (var discount in appliedDiscounts) { - if (discount.DiscountType == DiscountType.AssignedToSkus && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + if (discount.DiscountType == DiscountType.AssignedToSkus && !result.Contains(discount) && _discountService.IsDiscountValid(discount, customer)) { result.Add(discount); } @@ -133,7 +130,7 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer foreach (var discount in categoryDiscounts) { - if (discount.DiscountType == DiscountType.AssignedToCategories && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + if (discount.DiscountType == DiscountType.AssignedToCategories && !result.Contains(discount) && _discountService.IsDiscountValid(discount, customer)) { result.Add(discount); } @@ -166,7 +163,7 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer foreach (var discount in manuDiscounts) { - if (discount.DiscountType == DiscountType.AssignedToManufacturers && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + if (discount.DiscountType == DiscountType.AssignedToManufacturers && !result.Contains(discount) && _discountService.IsDiscountValid(discount, customer)) { result.Add(discount); } @@ -302,17 +299,17 @@ protected virtual decimal GetPreselectedPrice( var taxRate = decimal.Zero; var attributesTotalPriceBase = decimal.Zero; var preSelectedPriceAdjustmentBase = decimal.Zero; - var isBundle = (product.ProductType == ProductType.BundledProduct); - var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing); - var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing); - var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id); + var isBundle = product.ProductType == ProductType.BundledProduct; + var isBundleItemPricing = bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing; + var isBundlePricing = bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing; + var bundleItemId = bundleItem == null ? 0 : bundleItem.Item.Id; var query = new ProductVariantQuery(); var selectedAttributeValues = new List(); var attributes = context.Attributes.GetOrLoad(product.Id); // 1. Fill query with initially selected attributes. - foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Count > 0 && x.ShouldHaveValues())) + foreach (var attribute in attributes.Where(x => x.ProductVariantAttributeValues.Any() && x.ShouldHaveValues())) { int preSelectedValueId = 0; ProductVariantAttributeValue defaultValue = null; @@ -321,9 +318,9 @@ protected virtual decimal GetPreselectedPrice( foreach (var pvaValue in pvaValues) { - ProductBundleItemAttributeFilter attributeFilter = null; + ProductBundleItemAttributeFilter attributeFilter = null; - if (bundleItem.FilterOut(pvaValue, out attributeFilter)) + if (bundleItem?.Item?.FilterOut(pvaValue, out attributeFilter) ?? false) continue; if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected) @@ -374,7 +371,7 @@ protected virtual decimal GetPreselectedPrice( } // 2. Find attribute combination for selected attributes and merge it. - if (!isBundle && query.Variants.Count > 0) + if (!isBundle && query.Variants.Any()) { var attributeXml = query.CreateSelectedAttributesXml(product.Id, bundleItemId, attributes, _productAttributeParser, _services.Localization, _downloadService, _catalogSettings, _httpRequestBase, new List()); @@ -434,7 +431,8 @@ public virtual PriceCalculationContext CreatePriceCalculationContext( x => _categoryService.GetProductCategoriesByProductIds(x, true), x => _manufacturerService.GetProductManufacturersByProductIds(x), x => _productService.GetAppliedDiscountsByProductIds(x), - x => _productService.GetBundleItemsByProductIds(x, true) + x => _productService.GetBundleItemsByProductIds(x, true), + x => _productService.GetAssociatedProductsByProductIds(x) ); return context; @@ -481,8 +479,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 +492,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); } @@ -528,25 +523,27 @@ public virtual decimal GetFinalPrice( PriceCalculationContext context = null, bool isTierPrice = false) { - //initial price + // Initial price. decimal result = product.Price; - //special price + // Special price. var specialPrice = GetSpecialPrice(product); if (specialPrice.HasValue) + { result = specialPrice.Value; + } - if (isTierPrice) + if (isTierPrice) + { includeDiscounts = true; + } - //tier prices - if (product.HasTierPrices && !bundleItem.IsValid() && includeDiscounts) + // Tier prices. + if (product.HasTierPrices && includeDiscounts && !(bundleItem != null && bundleItem.Item != null)) { - - decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity, context); - Discount appliedDiscountTest = null; - decimal discountAmountTest = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscountTest, bundleItem); - decimal discountProductTest = result - discountAmountTest; + var tierPrice = GetMinimumTierPrice(product, customer, quantity, context); + var discountAmountTest = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscountTest, bundleItem); + var discountProductTest = result - discountAmountTest; //decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity); if (tierPrice.HasValue && tierPrice < discountProductTest) @@ -556,11 +553,10 @@ public virtual decimal GetFinalPrice( } } - //discount + additional charge + // Discount + additional charge. if (includeDiscounts) { - Discount appliedDiscount = null; - decimal discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out appliedDiscount, bundleItem, context); + var discountAmount = GetDiscountAmount(product, customer, additionalCharge, quantity, out var appliedDiscount, bundleItem, context); result = result + additionalCharge - discountAmount; } else @@ -569,7 +565,9 @@ public virtual decimal GetFinalPrice( } if (result < decimal.Zero) + { result = decimal.Zero; + } return result; } @@ -586,25 +584,24 @@ public virtual decimal GetFinalPrice( { if (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing) { - decimal result = decimal.Zero; - + var result = decimal.Zero; var items = bundleItems; + if (items == null) { - //var bundleEntities = context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)); items = context == null ? _productService.GetBundleItems(product.Id) - : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)); + : context.ProductBundleItems.GetOrLoad(product.Id).Select(x => new ProductBundleItemData(x)).ToList(); } - foreach (var itemData in items.Where(x => x.IsValid())) + foreach (var itemData in items.Where(x => x != null && x.Item != null)) { - decimal itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); + var itemPrice = GetFinalPrice(itemData.Item.Product, customer, itemData.AdditionalCharge, includeDiscounts, 1, itemData, context); - result = result + decimal.Multiply(itemPrice, itemData.Item.Quantity); + result += decimal.Multiply(itemPrice, itemData.Item.Quantity); } - return (result < decimal.Zero ? decimal.Zero : result); + return result < decimal.Zero ? decimal.Zero : result; } return GetFinalPrice(product, customer, additionalCharge, includeDiscounts, quantity, bundleItem, context); @@ -612,22 +609,21 @@ public virtual decimal GetFinalPrice( public virtual decimal GetLowestPrice(Product product, Customer customer, PriceCalculationContext context, out bool displayFromMessage) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); if (product.ProductType == ProductType.GroupedProduct) throw Error.InvalidOperation("Choose the other override for products of type grouped product."); - // note: attribute price adjustments were never regarded here cause of many reasons - - if (context == null) - context = CreatePriceCalculationContext(customer: customer); - - var isBundlePerItemPricing = (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing); + // Note, attribute price adjustments will not be taken into account here. + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } - displayFromMessage = isBundlePerItemPricing; + var isBundlePerItemPricing = product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing; + var lowestPrice = GetFinalPrice(product, null, customer, decimal.Zero, true, int.MaxValue, null, context); - var lowestPrice = GetFinalPrice(product, null, customer, decimal.Zero, true, int.MaxValue, null, context); + displayFromMessage = isBundlePerItemPricing; if (product.LowestAttributeCombinationPrice.HasValue && product.LowestAttributeCombinationPrice.Value < lowestPrice) { @@ -676,8 +672,10 @@ public virtual decimal GetLowestPrice(Product product, Customer customer, PriceC lowestPriceProduct = null; decimal? lowestPrice = null; - if (context == null) - context = CreatePriceCalculationContext(customer: customer); + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } foreach (var associatedProduct in associatedProducts) { @@ -695,43 +693,48 @@ public virtual decimal GetLowestPrice(Product product, Customer customer, PriceC } } - if (lowestPriceProduct == null) - lowestPriceProduct = associatedProducts.FirstOrDefault(); + if (lowestPriceProduct == null) + { + lowestPriceProduct = associatedProducts.FirstOrDefault(); + } return lowestPrice; } public virtual decimal GetPreselectedPrice(Product product, Customer customer, Currency currency, PriceCalculationContext context) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.NotNull(product, nameof(product)); var result = decimal.Zero; - if (context == null) - context = CreatePriceCalculationContext(customer: customer); - - if (product.ProductType == ProductType.BundledProduct) - { - var bundleItems = _productService.GetBundleItems(product.Id); + if (context == null) + { + context = CreatePriceCalculationContext(customer: customer); + } - var productIds = bundleItems.Select(x => x.Item.ProductId).ToList(); - productIds.Add(product.Id); + if (product.ProductType == ProductType.BundledProduct) + { + var bundleItems = context.ProductBundleItems + .GetOrLoad(product.Id) + .Select(x => new ProductBundleItemData(x)) + .ToList(); - context.Collect(productIds); + var productIds = bundleItems.Select(x => x.Item.ProductId).ToList(); + productIds.Add(product.Id); + context.Collect(productIds); - foreach (var bundleItem in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) - { - // fetch bundleItems.AdditionalCharge for all bundle items - var unused = GetPreselectedPrice(bundleItem.Item.Product, customer, currency, context, bundleItem, bundleItems); - } + // Fetch bundleItems.AdditionalCharge for all bundle items. + foreach (var bundleItem in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) + { + var unused = GetPreselectedPrice(bundleItem.Item.Product, customer, currency, context, bundleItem, bundleItems); + } - result = GetPreselectedPrice(product, customer, currency, context, null, bundleItems); - } - else - { - result = GetPreselectedPrice(product, customer, currency, context, null, null); - } + result = GetPreselectedPrice(product, customer, currency, context, null, bundleItems); + } + else + { + result = GetPreselectedPrice(product, customer, currency, context, null, null); + } return result; } @@ -799,8 +802,7 @@ public virtual decimal GetDiscountAmount(Product product, Customer customer, decimal additionalCharge) { - Discount appliedDiscount = null; - return GetDiscountAmount(product, customer, additionalCharge, out appliedDiscount); + return GetDiscountAmount(product, customer, additionalCharge, out var appliedDiscount); } /// @@ -832,7 +834,7 @@ public virtual decimal GetDiscountAmount( decimal appliedDiscountAmount = decimal.Zero; decimal finalPriceWithoutDiscount = decimal.Zero; - if (bundleItem.IsValid()) + if (bundleItem != null && bundleItem.Item != null) { if (bundleItem.Item.Discount.HasValue && bundleItem.Item.BundleProduct.BundlePerItemPricing) { @@ -912,7 +914,9 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml, _productAttributeParser); } - var bundleItems = shoppingCartItem.ChildItems.Where(x => x.BundleItemData.IsValid()).Select(x => x.BundleItemData).ToList(); + var bundleItems = shoppingCartItem.ChildItems + .Where(x => x.BundleItemData != null && x.BundleItemData.Item != null) + .Select(x => x.BundleItemData).ToList(); finalPrice = GetFinalPrice(product, bundleItems, customer, decimal.Zero, includeDiscounts, shoppingCartItem.Item.Quantity); } @@ -951,8 +955,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, /// Discount amount public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartItem) { - Discount appliedDiscount; - return GetDiscountAmount(shoppingCartItem, out appliedDiscount); + return GetDiscountAmount(shoppingCartItem, out var appliedDiscount); } /// @@ -997,16 +1000,17 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI public virtual decimal GetProductVariantAttributeValuePriceAdjustment(ProductVariantAttributeValue attributeValue, Product product, Customer customer, PriceCalculationContext context, int productQuantity = 1) { - if (attributeValue == null) - throw new ArgumentNullException("attributeValue"); + Guard.NotNull(attributeValue, nameof(attributeValue)); if (attributeValue.ValueType == ProductVariantAttributeValueType.Simple) { if (productQuantity > 1 && attributeValue.PriceAdjustment > 0) { var tierPriceAttributeAdjustment = GetTierPriceAttributeAdjustment(product, customer, productQuantity, context, attributeValue.PriceAdjustment); - if(tierPriceAttributeAdjustment != 0) + if (tierPriceAttributeAdjustment != 0) + { return tierPriceAttributeAdjustment; + } } return attributeValue.PriceAdjustment; @@ -1022,6 +1026,7 @@ public virtual decimal GetProductVariantAttributeValuePriceAdjustment(ProductVar return productPrice; } } + return decimal.Zero; } 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..7d3c6db822 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeFormatter.cs @@ -109,11 +109,11 @@ 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); - pvaAttribute = string.Format("{0}: {1}", attributeName, HtmlUtils.FormatText(valueStr, false, true, false, false, false, false)); + pvaAttribute = string.Format("{0}: {1}", attributeName, HtmlUtils.ConvertPlainTextToHtml(valueStr.HtmlEncode())); //we never encode multiline textbox input } else if (pva.AttributeControlType == AttributeControlType.FileUpload) @@ -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/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 2f9c020ad8..7c6f33ce38 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -19,8 +19,9 @@ public partial class ProductAttributeParser : IProductAttributeParser // 0 = AttributeXml private const string ATTRIBUTEVALUES_BY_XML_KEY = "parsedattributevalues-{0}"; + private const string ATTRIBUTEVALUES_PATTERN_KEY = "parsedattributevalues-*"; - private readonly IProductAttributeService _productAttributeService; + private readonly IProductAttributeService _productAttributeService; private readonly IRepository _pvacRepository; private readonly IRequestCache _requestCache; @@ -40,10 +41,12 @@ public ProductAttributeParser( #region Product attributes - public virtual void PrefetchProductVariantAttributes(IEnumerable attributesXml) + public virtual int PrefetchProductVariantAttributes(IEnumerable attributesXml) { - if (attributesXml == null || !attributesXml.Any()) - return; + if (attributesXml == null || !attributesXml.Any()) + { + return 0; + } // Determine uncached attributes var unfetched = attributesXml @@ -110,7 +113,9 @@ where id.HasValue() var cacheKey = ATTRIBUTEVALUES_BY_XML_KEY.FormatInvariant(info.AttributesXml); _requestCache.Put(cacheKey, cachedValues); } - } + + return unfetched.Length; + } public virtual IList ParseProductVariantAttributes(string attributesXml) { @@ -156,7 +161,12 @@ where id.HasValue() return result; } - public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) + public virtual void ClearCachedAttributeValues() + { + _requestCache.RemoveByPattern(ATTRIBUTEVALUES_PATTERN_KEY); + } + + public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) { var attrs = new Multimap(); if (String.IsNullOrEmpty(attributesXml)) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index ec7665e87a..818343d2ef 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 @@ -282,6 +321,11 @@ public virtual Multimap GetProductVariantAttribute { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + var query = from pva in _productVariantAttributeRepository.TableUntracked.Expand(x => x.ProductAttribute).Expand(x => x.ProductVariantAttributeValues) where productIds.Contains(pva.ProductId) @@ -363,12 +407,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(); }); } @@ -623,6 +669,11 @@ public virtual Multimap GetProductVaria { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + var query = from pvac in _pvacRepository.TableUntracked where productIds.Contains(pvac.ProductId) @@ -662,13 +713,37 @@ public virtual ProductVariantAttributeCombination GetProductVariantAttributeComb public virtual ProductVariantAttributeCombination GetProductVariantAttributeCombinationBySku(string sku) { - if (sku.IsEmpty()) - return null; + 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); return combination; } + public virtual ProductVariantAttributeCombination GetAttributeCombinationByGtin(string gtin) + { + if (gtin.IsEmpty()) + { + return null; + } + + var combination = _pvacRepository.Table.FirstOrDefault(x => x.Gtin == gtin && !x.Product.Deleted); + return combination; + } + + public virtual ProductVariantAttributeCombination GetAttributeCombinationByMpn(string manufacturerPartNumber) + { + if (manufacturerPartNumber.IsEmpty()) + { + return null; + } + + var combination = _pvacRepository.Table.FirstOrDefault(x => x.ManufacturerPartNumber == manufacturerPartNumber && !x.Product.Deleted); + return combination; + } + public virtual void InsertProductVariantAttributeCombination(ProductVariantAttributeCombination combination) { if (combination == null) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index f84c7379c1..679dddfea8 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); + } + else if (product.MergedDataValues != null) { - product.MergeWithCombination(combination); + product.MergedDataValues.Clear(); } return combination; @@ -103,12 +104,11 @@ public static IList GetAllCombinationPictureIds(this IEnumerable 0) { - int id; var ids = string.Join(",", data).SplitSafe(",").Distinct(); foreach (string str in ids) { - if (int.TryParse(str, out id) && !pictureIds.Exists(i => i == id)) + if (int.TryParse(str, out var id) && !pictureIds.Exists(i => i == id)) pictureIds.Add(id); } } @@ -144,8 +144,11 @@ public static CrossSellProduct FindCrossSellProduct(this IList int productId1, int productId2) { foreach (CrossSellProduct crossSellProduct in source) + { if (crossSellProduct.ProductId1 == productId1 && crossSellProduct.ProductId2 == productId2) return crossSellProduct; + } + return null; } @@ -155,9 +158,12 @@ public static bool IsAvailableByStock(this Product product) if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock || product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) { - if (product.StockQuantity <= 0 && product.BackorderMode == BackorderMode.NoBackorders) - return false; + if (product.StockQuantity <= 0 && product.BackorderMode == BackorderMode.NoBackorders) + { + return false; + } } + return true; } @@ -199,8 +205,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 +218,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) { @@ -253,8 +279,7 @@ public static int[] ParseAllowedQuatities(this Product product) .ToList() .ForEach(qtyStr => { - int qty = 0; - if (int.TryParse(qtyStr.Trim(), out qty)) + if (int.TryParse(qtyStr.Trim(), out var qty)) { result.Add(qty); } @@ -277,8 +302,7 @@ public static int[] ParseRequiredProductIds(this Product product) .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim())) { - int id = 0; - if (int.TryParse(idStr, out id)) + if (int.TryParse(idStr, out var id)) ids.Add(id); } @@ -297,7 +321,6 @@ public static int[] ParseRequiredProductIds(this Product product) /// Customer /// Target currency /// Price adjustment - /// Whether the result string should be language insensitive /// The base price info public static string GetBasePriceInfo(this Product product, ILocalizationService localizationService, @@ -307,8 +330,7 @@ public static string GetBasePriceInfo(this Product product, IPriceCalculationService priceCalculationService, Customer customer, Currency currency, - decimal priceAdjustment = decimal.Zero, - bool languageInsensitive = false) + decimal priceAdjustment = decimal.Zero) { Guard.NotNull(product, nameof(product)); Guard.NotNull(currencyService, nameof(currencyService)); @@ -319,16 +341,14 @@ public static string GetBasePriceInfo(this Product product, if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero) { - var taxrate = decimal.Zero; var currentPrice = priceCalculationService.GetFinalPrice(product, customer, true); - var price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), customer, currency, out taxrate); - + var price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), customer, currency, out var taxrate); price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); - return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency, languageInsensitive); + return product.GetBasePriceInfo(price, localizationService, priceFormatter, currency); } - return ""; + return string.Empty; } /// @@ -339,14 +359,12 @@ public static string GetBasePriceInfo(this Product product, /// Localization service /// Price formatter /// Target currency - /// Whether the result string should be language insensitive /// The base price info public static string GetBasePriceInfo(this Product product, decimal productPrice, ILocalizationService localizationService, IPriceFormatter priceFormatter, - Currency currency, - bool languageInsensitive = false) + Currency currency) { Guard.NotNull(product, nameof(product)); Guard.NotNull(localizationService, nameof(localizationService)); @@ -358,8 +376,7 @@ public static string GetBasePriceInfo(this Product product, var value = Convert.ToDecimal((productPrice / product.BasePriceAmount) * product.BasePriceBaseAmount); var valueFormatted = priceFormatter.FormatPrice(value, true, currency); var amountFormatted = Math.Round(product.BasePriceAmount.Value, 2).ToString("G29"); - - var infoTemplate = localizationService.GetResource(languageInsensitive ? "Products.BasePriceInfo.LanguageInsensitive" : "Products.BasePriceInfo"); + var infoTemplate = localizationService.GetResource("Products.BasePriceInfo"); var result = infoTemplate.FormatInvariant( amountFormatted, @@ -371,40 +388,38 @@ public static string GetBasePriceInfo(this Product product, return result; } - return ""; + return string.Empty; } public static string GetProductTypeLabel(this Product product, ILocalizationService localizationService) { if (product != null && product.ProductType != ProductType.SimpleProduct) { - string key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatWith(product.ProductType.ToString()); + var key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatInvariant(product.ProductType.ToString()); return localizationService.GetResource(key); } - return ""; + + return string.Empty; } public static bool CanBeBundleItem(this Product product) { - return (product != null && product.ProductType == ProductType.SimpleProduct && !product.IsRecurring && !product.IsDownload); + return product != null && product.ProductType == ProductType.SimpleProduct && !product.IsRecurring && !product.IsDownload; } - public static bool IsValid(this ProductBundleItemData bundleItemData) - { - return (bundleItemData != null && bundleItemData.Item != null); - } - public static bool FilterOut(this ProductBundleItemData bundleItemData, ProductVariantAttributeValue value, out ProductBundleItemAttributeFilter filter) - { - if (bundleItemData.IsValid() && value != null && bundleItemData.Item.FilterAttributes) - { - filter = bundleItemData.Item.AttributeFilters.FirstOrDefault(x => x.AttributeId == value.ProductVariantAttributeId && x.AttributeValueId == value.Id); + public static bool FilterOut(this ProductBundleItem bundleItem, ProductVariantAttributeValue value, out ProductBundleItemAttributeFilter filter) + { + if (bundleItem != null && value != null && bundleItem.FilterAttributes) + { + filter = bundleItem.AttributeFilters.FirstOrDefault(x => x.AttributeId == value.ProductVariantAttributeId && x.AttributeValueId == value.Id); + return filter == null; + } - return (filter == null); - } - filter = null; - return false; - } - public static string GetLocalizedName(this ProductBundleItem bundleItem) + filter = null; + return false; + } + + public static string GetLocalizedName(this ProductBundleItem bundleItem) { if (bundleItem != null) { @@ -426,8 +441,10 @@ public static string GetLocalizedName(this ProductBundleItem bundleItem, int lan public static ProductBundleItemOrderData ToOrderData(this ProductBundleItemData bundleItemData, decimal priceWithDiscount = decimal.Zero, string attributesXml = null, string attributesInfo = null) { - if (!bundleItemData.IsValid()) - return null; + if (bundleItemData == null || bundleItemData.Item == null) + { + return null; + } var item = bundleItemData.Item; string bundleItemName = item.GetLocalized(x => x.Name); @@ -437,7 +454,7 @@ public static ProductBundleItemOrderData ToOrderData(this ProductBundleItemData BundleItemId = item.Id, ProductId = item.ProductId, Sku = item.Product.Sku, - ProductName = (bundleItemName ?? item.Product.GetLocalized(x => x.Name)), + ProductName = bundleItemName ?? item.Product.GetLocalized(x => x.Name), ProductSeName = item.Product.GetSeName(), VisibleIndividually = item.Product.VisibleIndividually, Quantity = item.Quantity, diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 81fc1ac527..bd20bc65e9 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 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 select rp).ToList(); foreach (int id2 in productIds) @@ -189,8 +189,10 @@ public virtual Product GetProductById(int productId) public virtual IList GetProductsByIds(int[] productIds, ProductLoadFlags flags = ProductLoadFlags.None) { - if (productIds == null || productIds.Length == 0) + if (productIds == null || !productIds.Any()) + { return new List(); + } var query = from p in _productRepository.Table where productIds.Contains(p.Id) @@ -203,10 +205,21 @@ where productIds.Contains(p.Id) var products = query.ToList(); - // sort by passed identifier sequence + // Sort by passed identifier sequence. 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 +339,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.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && p.MinStockQuantity >= p.StockQuantity select p; @@ -335,7 +348,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.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && pvac.StockQuantity <= 0 select p; @@ -378,8 +391,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.Gtin == gtin select p; var product = query.FirstOrDefault(); return product; @@ -586,7 +598,13 @@ public virtual Multimap GetProductTagsByProductIds(int[] produc { Guard.NotNull(productIds, nameof(productIds)); - var query = _productRepository.TableUntracked + var map = new Multimap(); + if (!productIds.Any()) + { + return map; + } + + var query = _productRepository.TableUntracked .Expand(x => x.ProductTags) .Where(x => productIds.Contains(x.Id)) .Select(x => new @@ -595,9 +613,9 @@ public virtual Multimap GetProductTagsByProductIds(int[] produc Tags = x.ProductTags }); - 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); @@ -606,11 +624,40 @@ public virtual Multimap GetProductTagsByProductIds(int[] produc return map; } - public virtual Multimap GetAppliedDiscountsByProductIds(int[] productIds) + public virtual Multimap GetAssociatedProductsByProductIds(int[] productIds, bool showHidden = false) + { + Guard.NotNull(productIds, nameof(productIds)); + + if (!productIds.Any()) + { + return new Multimap(); + } + + // Ignore multistore. Expect multistore setting for associated products is the same as for parent grouped product. + var query = _productRepository.TableUntracked + .Where(x => productIds.Contains(x.ParentGroupedProductId) && !x.Deleted && (showHidden || x.Published)) + .OrderBy(x => x.ParentGroupedProductId) + .ThenBy(x => x.DisplayOrder); + + var associatedProducts = query.ToList(); + + var map = associatedProducts + .ToMultimap(x => x.ParentGroupedProductId, x => x); + + return map; + } + + public virtual Multimap GetAppliedDiscountsByProductIds(int[] productIds) { Guard.NotNull(productIds, nameof(productIds)); - - var query = _productRepository.Table // .TableUntracked does not seem to eager load + + var map = new Multimap(); + if (!productIds.Any()) + { + return map; + } + + var query = _productRepository.Table // .TableUntracked does not seem to eager load .Expand(x => x.AppliedDiscounts.Select(y => y.DiscountRequirements)) .Where(x => productIds.Contains(x.Id)) .Select(x => new @@ -619,8 +666,6 @@ public virtual Multimap GetAppliedDiscountsByProductIds(int[] pro Discounts = x.AppliedDiscounts }); - var map = new Multimap(); - foreach (var item in query.ToList()) { map.AddRange(item.ProductId, item.Discounts); @@ -805,6 +850,11 @@ public virtual Multimap GetTierPricesByProductIds(int[] productI { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + var query = from x in _tierPriceRepository.TableUntracked where productIds.Contains(x.ProductId) @@ -895,7 +945,14 @@ orderby pp.DisplayOrder public virtual Multimap GetProductPicturesByProductIds(int[] productIds, bool onlyFirstPicture = false) { - var query = + Guard.NotNull(productIds, nameof(productIds)); + + if (!productIds.Any()) + { + return new Multimap(); + } + + var query = from pp in _productPictureRepository.TableUntracked.Expand(x => x.Picture) where productIds.Contains(pp.ProductId) orderby pp.ProductId, pp.DisplayOrder @@ -1014,7 +1071,7 @@ public virtual ProductBundleItem GetBundleItemById(int bundleItemId) return _productBundleItemRepository.GetById(bundleItemId); } - public virtual IList GetBundleItems(int bundleProductId, bool showHidden = false) + public virtual IList GetBundleItems(int bundleProductId, bool showHidden = false) { var query = from pbi in _productBundleItemRepository.Table @@ -1023,7 +1080,7 @@ join p in _productRepository.Table on pbi.ProductId equals p.Id orderby pbi.DisplayOrder select pbi; - query = query.Expand(x => x.Product); + query = query.Include(x => x.Product); var bundleItemData = new List(); @@ -1032,10 +1089,15 @@ orderby pbi.DisplayOrder return bundleItemData; } - public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) + public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + var query = from pbi in _productBundleItemRepository.TableUntracked join p in _productRepository.TableUntracked on pbi.ProductId equals p.Id @@ -1043,13 +1105,32 @@ where productIds.Contains(pbi.BundleProductId) && !p.Deleted && (showHidden || ( orderby pbi.DisplayOrder select pbi; - var map = query.Expand(x => x.Product) - .ToList() + var map = query + .Include(x => x.Product) + .Include(x => x.BundleProduct) + .ToList() .ToMultimap(x => x.BundleProductId, x => x); return map; } - #endregion - } + public virtual bool IsBundleItem(int productId) + { + if (productId == 0) + { + return false; + } + + var query = + from pbi in _productBundleItemRepository.TableUntracked + join bundle in _productRepository.TableUntracked on pbi.BundleProductId equals bundle.Id + where pbi.ProductId == productId && !bundle.Deleted + select pbi; + + var result = query.Any(); + return result; + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs index b8f94ad350..ac5bda82b6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/SpecificationAttributeService.cs @@ -105,10 +105,16 @@ public virtual SpecificationAttributeOption GetSpecificationAttributeOptionById( public virtual IList GetSpecificationAttributeOptionsBySpecificationAttribute(int specificationAttributeId) { + if (specificationAttributeId == 0) + { + return new List(); + } + var query = from sao in _specificationAttributeOptionRepository.Table orderby sao.DisplayOrder where sao.SpecificationAttributeId == specificationAttributeId select sao; + var specificationAttributeOptions = query.ToList(); return specificationAttributeOptions; } @@ -179,7 +185,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 { @@ -220,8 +226,12 @@ public virtual Multimap GetProductSpecificat { Guard.NotNull(productIds, nameof(productIds)); + if (!productIds.Any()) + { + return new Multimap(); + } + 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/Cms/Blocks/BlockHandlerBase.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/BlockHandlerBase.cs new file mode 100644 index 0000000000..03d182c4a3 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/BlockHandlerBase.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Mvc.Async; +using System.Web.Mvc.Html; +using System.Web.Routing; +using Newtonsoft.Json; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Utilities; + +namespace SmartStore.Services.Cms.Blocks +{ + public abstract class BlockHandlerBase : IBlockHandler where T : IBlock + { + public ICommonServices Services { get; set; } + + public ILogger Logger { get; set; } + + public ILocalizedEntityService LocalizedEntityService { get; set; } + + public virtual T Create(IBlockEntity entity) + { + return Activator.CreateInstance(); + } + + public virtual T Load(IBlockEntity entity, StoryViewMode viewMode) + { + Guard.NotNull(entity, nameof(entity)); + + var block = Create(entity); + var json = entity.Model; + + if (json.IsEmpty()) + { + return block; + } + + JsonConvert.PopulateObject(json, block); + + if (block is IBindableBlock bindableBlock) + { + bindableBlock.BindEntityName = entity.BindEntityName; + bindableBlock.BindEntityId = entity.BindEntityId; + } + + return block; + } + + public virtual bool IsValid(T block) + { + return true; + } + + public virtual void Save(T block, IBlockEntity entity) + { + Guard.NotNull(entity, nameof(entity)); + + if (block == null) + { + return; + } + + var settings = new JsonSerializerSettings + { + TypeNameHandling = TypeNameHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + ObjectCreationHandling = ObjectCreationHandling.Replace, + NullValueHandling = NullValueHandling.Ignore + }; + + entity.Model = JsonConvert.SerializeObject(block, Formatting.None, settings); + + // save BindEntintyName & BindEntintyId + if (block is IBindableBlock bindableBlock) + { + entity.BindEntityId = bindableBlock.BindEntityId; + entity.BindEntityName = bindableBlock.BindEntityName; + } + } + + public virtual string Clone(IBlockEntity sourceEntity, IBlockEntity clonedEntity) + { + return sourceEntity.Model; + } + + public void Render(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper) + { + RenderCore(element, templates, htmlHelper, htmlHelper.ViewContext.Writer); + } + + public IHtmlString ToHtmlString(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper) + { + using (var writer = new StringWriter(CultureInfo.CurrentCulture)) + { + RenderCore(element, templates, htmlHelper, writer); + return MvcHtmlString.Create(writer.ToString()); + } + } + + protected virtual void RenderCore(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper, TextWriter textWriter) + { + RenderByView(element, templates, htmlHelper, textWriter); + } + + protected void RenderByView(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper, TextWriter textWriter) + { + Guard.NotNull(element, nameof(element)); + Guard.NotNull(templates, nameof(templates)); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); + Guard.NotNull(textWriter, nameof(textWriter)); + + var viewContext = htmlHelper.ViewContext; + + if (!element.Metadata.IsInbuilt) + { + // Change "area" token in RouteData in order to begin search in the plugin's view folder. + var originalRouteData = htmlHelper.ViewContext.RouteData; + var routeData = new RouteData(originalRouteData.Route, originalRouteData.RouteHandler); + routeData.Values.Merge(originalRouteData.Values); + routeData.DataTokens["area"] = element.Metadata.AreaName; + + viewContext = new ViewContext + { + RouteData = routeData, + RequestContext = htmlHelper.ViewContext.RequestContext + }; + } + + var viewResult = FindFirstView(element.Metadata, templates, viewContext, out var searchedLocations); + + if (viewResult == null) + { + var msg = string.Format("No template found for '{0}'. Searched locations:\n{1}.", string.Join(", ", templates), string.Join("\n", searchedLocations)); + Logger.Debug(msg); + throw new FileNotFoundException(msg); + } + + var viewData = new ViewDataDictionary(element.Block); + viewData.TemplateInfo.HtmlFieldPrefix = "Block"; + + viewContext = new ViewContext( + htmlHelper.ViewContext, + viewResult.View, + viewData, + htmlHelper.ViewContext.TempData, + textWriter); + + viewResult.View.Render(viewContext, textWriter); + } + + private ViewEngineResult FindFirstView(IBlockMetadata blockMetadata, IEnumerable templates, ViewContext viewContext, out ICollection searchedLocations) + { + searchedLocations = new List(); + + foreach (var template in templates) + { + var viewName = string.Concat("BlockTemplates/", blockMetadata.SystemName, "/", template); + var viewResult = ViewEngines.Engines.FindPartialView(viewContext, viewName); + searchedLocations.AddRange(viewResult.SearchedLocations); + if (viewResult.View != null) + { + return viewResult; + } + } + + return null; + } + + protected void RenderByChildAction(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper, TextWriter textWriter) + { + Guard.NotNull(element, nameof(element)); + Guard.NotNull(templates, nameof(templates)); + Guard.NotNull(htmlHelper, nameof(htmlHelper)); + Guard.NotNull(textWriter, nameof(textWriter)); + + var routeInfo = templates.Select(x => GetRoute(element, x)).FirstOrDefault(); + if (routeInfo == null) + { + throw new InvalidOperationException("The return value of the 'GetRoute()' method cannot be NULL."); + } + + var routeValues = routeInfo.RouteValues; + + routeValues["action"] = routeInfo.Action; + routeValues["controller"] = routeInfo.Controller; + + VirtualPathData vpd = GetVirtualPathForArea(htmlHelper.RouteCollection, htmlHelper.ViewContext.RequestContext, null /* name */, routeValues, out var usingAreas); + if (vpd == null) + { + throw new InvalidOperationException("Could not find any matching route."); + } + + if (usingAreas) + { + routeValues.Remove("area"); + } + + var routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext); + var httpContext = htmlHelper.ViewContext.HttpContext; + var requestContext = new RequestContext(httpContext, routeData); + + // Create the controller instance + var controller = ControllerBuilder.Current.GetControllerFactory().CreateController(requestContext, routeInfo.Controller) as Controller; + if (controller == null) + { + throw new InvalidOperationException($"Could not activate controller '{routeInfo.Controller}'. Please ensure that the controller class exists and inherits from '{typeof(Controller).FullName}'."); + } + + var originalWriter = htmlHelper.ViewContext.Writer; + htmlHelper.ViewContext.Writer = textWriter; + + var originalOutput = httpContext.Response.Output; + httpContext.Response.Output = textWriter; + + var originalActionInvoker = controller.ActionInvoker; + controller.ActionInvoker = new ActionInvokerWithResultValidator(); + + void endRender() + { + htmlHelper.ViewContext.Writer = originalWriter; + httpContext.Response.Output = originalOutput; + controller.ActionInvoker = originalActionInvoker; + } + + using (new ActionDisposable((Action)endRender)) + { + ((IController)controller).Execute(requestContext); + } + } + + #region Legacy 'RenderByChildAction' + + //protected void RenderByChildAction(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper, TextWriter textWriter) + // { + // Guard.NotNull(element, nameof(element)); + // Guard.NotNull(templates, nameof(templates)); + // Guard.NotNull(htmlHelper, nameof(htmlHelper)); + // Guard.NotNull(textWriter, nameof(textWriter)); + + // var routeInfo = templates.Select(x => GetRoute(element, x)).FirstOrDefault(); + // if (routeInfo == null) + // { + // throw new InvalidOperationException("The return value of the 'GetRoute()' method cannot be NULL."); + // } + + // //routeInfo.RouteValues["model"] = element.Block; + + // var originalWriter = htmlHelper.ViewContext.Writer; + // htmlHelper.ViewContext.Writer = textWriter; + + // using (new ActionDisposable(() => htmlHelper.ViewContext.Writer = originalWriter)) + // { + // htmlHelper.RenderAction(routeInfo.Action, routeInfo.Controller, routeInfo.RouteValues); + // } + // } + + #endregion + + protected virtual RouteInfo GetRoute(IBlockContainer element, string template) + => throw new NotImplementedException(); + + + class ActionInvokerWithResultValidator : AsyncControllerActionInvoker + { + protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) + { + switch (actionResult) + { + case PartialViewResult _: + case ContentResult _: + base.InvokeActionResult(controllerContext, actionResult); + break; + default: + throw new InvalidOperationException($"The action result type of an MVC route block must either be '{nameof(PartialViewResult)}' or '{nameof(ContentResult)}'"); + } + } + } + + #region Copied from ASP.NET MVC + + private VirtualPathData GetVirtualPathForArea(RouteCollection routes, RequestContext requestContext, string name, RouteValueDictionary values, out bool usingAreas) + { + // Copied over from https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/RouteCollectionExtensions.cs#L53 + + Guard.NotNull(routes, nameof(routes)); + + if (!String.IsNullOrEmpty(name)) + { + // the route name is a stronger qualifier than the area name, so just pipe it through + usingAreas = false; + return routes.GetVirtualPath(requestContext, name, values); + } + + string targetArea = null; + if (values != null) + { + if (values.TryGetValue("area", out var targetAreaRawValue)) + { + targetArea = targetAreaRawValue as string; + } + else + { + // set target area to current area + if (requestContext != null) + { + targetArea = requestContext.RouteData.GetAreaName(); + } + } + } + + // need to apply a correction to the RVD if areas are in use + RouteValueDictionary correctedValues = values; + RouteCollection filteredRoutes = FilterRouteCollectionByArea(routes, targetArea, out usingAreas); + if (usingAreas) + { + correctedValues = new RouteValueDictionary(values); + correctedValues.Remove("area"); + } + + VirtualPathData vpd = filteredRoutes.GetVirtualPath(requestContext, correctedValues); + return vpd; + } + + // This method returns a new RouteCollection containing only routes that matched a particular area. + // The Boolean out parameter is just a flag specifying whether any registered routes were area-aware. + private RouteCollection FilterRouteCollectionByArea(RouteCollection routes, string areaName, out bool usingAreas) + { + // Copied over from https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/RouteCollectionExtensions.cs#L18 + + if (areaName == null) + { + areaName = String.Empty; + } + + usingAreas = false; + + // Ensure that we continue using the same settings as the previous route collection + // if we are using areas and the route collection is exchanged + RouteCollection filteredRoutes = new RouteCollection + { + AppendTrailingSlash = routes.AppendTrailingSlash, + LowercaseUrls = routes.LowercaseUrls, + RouteExistingFiles = routes.RouteExistingFiles + }; + + using (routes.GetReadLock()) + { + foreach (RouteBase route in routes) + { + string thisAreaName = route.GetAreaName() ?? String.Empty; + usingAreas |= (thisAreaName.Length > 0); + if (String.Equals(thisAreaName, areaName, StringComparison.OrdinalIgnoreCase)) + { + filteredRoutes.Add(route); + } + } + } + + // if areas are not in use, the filtered route collection might be incorrect + return (usingAreas) ? filteredRoutes : routes; + } + + private RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext) + { + var routeData = new RouteData(); + + foreach (var kvp in routeValues) + { + routeData.Values.Add(kvp.Key, kvp.Value); + } + + foreach (var kvp in dataTokens) + { + routeData.DataTokens.Add(kvp.Key, kvp.Value); + } + + routeData.Route = route; + routeData.DataTokens["ParentActionViewContext"] = parentViewContext; + + return routeData; + } + + class ChildActionMvcHandler : MvcHandler + { + public ChildActionMvcHandler(RequestContext context) + : base(context) + { + } + + protected override void AddVersionHeader(HttpContextBase httpContext) + { + // No version header for child actions + } + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/BlockMetadata.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/BlockMetadata.cs new file mode 100644 index 0000000000..ee65bc8ff5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/BlockMetadata.cs @@ -0,0 +1,79 @@ +using System; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Applies metadata to concrete block types which implement . + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class BlockAttribute : Attribute + { + public BlockAttribute(string systemName) + { + Guard.NotNull(systemName, nameof(systemName)); + + SystemName = systemName; + } + + /// + /// The block system name, e.g. 'html', 'picture' etc. + /// + public string SystemName { get; set; } + + /// + /// The english friendly name of the block + /// + public string FriendlyName { get; set; } + + /// + /// The icon class name of the block, e.g. 'fa fa-sitemap' + /// + public string Icon { get; set; } + + /// + /// The order of display + /// + public int DisplayOrder { get; set; } + + public bool IsInternal { get; set; } + } + + /// + /// Represents block registration metadata + /// + public interface IBlockMetadata : IProviderMetadata + { + string AreaName { get; } + string Icon { get; } + bool IsInternal { get; } + bool IsInbuilt { get; } + Type BlockClrType { get; } + Type BlockHandlerClrType { get; } + } + + public class BlockMetadata : IBlockMetadata, ICloneable + { + public string AreaName { get; set; } + public string SystemName { get; set; } + public string FriendlyName { get; set; } + public string Description { get; set; } + public string ResourceKeyPattern { get; set; } + public string Icon { get; set; } + public int DisplayOrder { get; set; } + public bool IsInternal { get; set; } + public bool IsInbuilt { get; set; } + public Type BlockClrType { get; set; } + public Type BlockHandlerClrType { get; set; } + + public BlockMetadata Clone() + { + return (BlockMetadata)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlock.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlock.cs new file mode 100644 index 0000000000..3638b5e31c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlock.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// When implemented on types, makes the block bindable to + /// product, category and manufacturer entities. The UI will display a 'Data binding' + /// section which allows selection of an entity. + /// + /// + /// The handler for a bindable block type MUST implement . + /// + public interface IBindableBlock : IBlock + { + /// + /// The name of the bound entity, e.g. 'product'. + /// + [JsonIgnore] + string BindEntityName { get; set; } + + /// + /// The id of the bound entity. + /// + [JsonIgnore] + int? BindEntityId { get; set; } + + /// + /// Returns a value to indicate whether the block can be bound. A block can be bound + /// if both and have values. + /// However, this property does not indicate whether the bound entity actually exists. + /// + bool CanBind { get; } + + /// + /// Returns a value to indicate whether the binding source () has been loaded from the store already. + /// + bool IsLoaded { get; } + + /// + /// The data item of the bound entity. + /// + IDictionary DataItem { get; set; } + + /// + /// Returns a value to indicate whether the block has been bound already. + /// A block is considered bound if the binding source dictionary has been + /// applied to the block instance's bindable properties. + /// + bool IsBound { get; set; } + + /// + /// Resets the data item of the bound entity. After calling this method, + /// is null, and are false. + /// + void Reset(); + } + + public abstract class BindableBlockBase : IBindableBlock + { + public virtual string BindEntityName { get; set; } + public virtual int? BindEntityId { get; set; } + + [JsonIgnore] + public bool CanBind + { + get + { + return BindEntityName.HasValue() && BindEntityId.HasValue; + } + } + + [JsonIgnore] + public bool IsLoaded + { + get { return DataItem != null; } + } + + [JsonIgnore] + public IDictionary DataItem { get; set; } + + [JsonIgnore] + public bool IsBound { get; set; } + + public void Reset() + { + DataItem = null; + IsBound = false; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlockHandler.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlockHandler.cs new file mode 100644 index 0000000000..0658902f37 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBindableBlockHandler.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Mvc; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Retrieves information about UI mapping between template tokens and form controls. + /// + /// + /// only works in conjunction with . + /// Make sure that the handled block type implements . + /// + public interface IBindableBlockHandler : IBlockHandler + { + /// + /// Build and returns the template mapping configuration. + /// + /// + TemplateMappingConfiguration GetMappingConfiguration(); + } + + /// + /// Contans information about UI mapping between template tokens and form controls. + /// + public class TemplateMappingConfiguration + { + /// + /// A list of template tokens and their corresponding form controls. + /// + public IList Map { get; set; } + + /// + /// The complete list of field names which can be bound to entity fields. + /// + public string[] BindableFields { get; set; } + } + + /// + /// Block data binding mapping definition for a single field. + /// + public class TemplateMapping + { + /// + /// Name of the bound field, e.g. 'Title', 'Description', 'Price' etc. + /// + public string FieldName { get; set; } + + /// + /// The 'Liquid' template + /// + public string Template { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBlock.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlock.cs new file mode 100644 index 0000000000..5f08fb785d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlock.cs @@ -0,0 +1,13 @@ +using System; +using System.Web.Routing; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Marker interface for a PageBuilder block implementation. + /// Blocks are handled by corresponding implementations. + /// + public interface IBlock + { + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockContainer.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockContainer.cs new file mode 100644 index 0000000000..9f5e50c931 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockContainer.cs @@ -0,0 +1,51 @@ +using System; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Contains base data for a block instance. + /// + public interface IBlockContainer + { + /// + /// The type of the block, e.g. 'html'. + /// + string BlockType { get; } + + /// + /// The actual block instance. + /// + IBlock Block { get; } + + /// + /// The block metadata. + /// + IBlockMetadata Metadata { get; } + + /// + /// The block title / display name. + /// + string Title { get; } + + /// + /// Whether the block's text content color should be inversed so + /// that it gets readable on dark backgrounds. + /// + bool IsInversed { get; } + + /// + /// The block html id. + /// + string HtmlId { get; } + + /// + /// The block css class name(s). + /// + string CssClass { get; } + + /// + /// The block css style expression. + /// + string CssStyle { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockEntity.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockEntity.cs new file mode 100644 index 0000000000..100f78e138 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockEntity.cs @@ -0,0 +1,28 @@ +using System; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Represents a block instance's storage data. + /// + public interface IBlockEntity + { + int Id { get; set; } + int StoryId { get; } + string BlockType { get; set; } + string Model { get; set; } + string TagLine { get; set; } + string Title { get; set; } + string SubTitle { get; set; } + string Body { get; set; } + string Template { get; set; } + string Custom1 { get; set; } + string Custom2 { get; set; } + string Custom3 { get; set; } + string Custom4 { get; set; } + string Custom5 { get; set; } + + string BindEntityName { get; set; } + int? BindEntityId { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockHandler.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockHandler.cs new file mode 100644 index 0000000000..8f50bfdb37 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/IBlockHandler.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Web; +using System.Web.Mvc; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Indicates the view mode of a PageBuilder block. + /// + public enum StoryViewMode + { + /// + /// Final rendering result in public frontend + /// + Public, + /// + /// Preview mode in backend + /// + Preview, + /// + /// Visual block editing in backend's story editor + /// + GridEdit, + /// + /// Property dialog in backend + /// + Edit + } + + /// + /// Handles rendering of corresponding implementations. + /// + public interface IBlockHandler + { + /// + /// Renders the block result. + /// + /// The block element to render. + /// A list of template names. The first valid template will be used for rendering. + /// Html helper instance. + void Render(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHeper); + + /// + /// returns the block's render result as string. + /// + /// The block element to render. + /// A list of valid template names. The first valid template will be used for rendering. + /// Html helper instance. + IHtmlString ToHtmlString(IBlockContainer element, IEnumerable templates, HtmlHelper htmlHelper); + } + + /// + /// Handles loading, saving and rendering of corresponding implementations. + /// + public interface IBlockHandler : IBlockHandler where T : IBlock + { + /// + /// Creates the implementation instance for the passed block entity. + /// + /// The serialized entity to create a block instance for. + /// A concrete block implementation instance. + T Create(IBlockEntity entity); + + /// + /// Creates and loads the implementation instance for the passed block entity and view mode. + /// + /// The serialized entity to load a block instance for. + /// Depending on view mode, some data may be loaded or not. + /// A concrete block implementation instance. + T Load(IBlockEntity entity, StoryViewMode viewMode); + + bool IsValid(T block); + + /// + /// Serializes the passed block instance and saves the result in the underlying data storage. + /// + /// The block instance to save. + /// + /// The corresponding entity record for the block. In most cases will + /// be converted to JSON and assigned to property, which then will be saved in the data storage. + /// + void Save(T block, IBlockEntity entity); + + /// + /// Clones . In most cases it is sufficient to directly return the serialized model, but there may be cases + /// where the inner data must be cloned first, e.g. if it contains a picture reference. + /// + /// The source entity. + /// The target entity clone which is about to be saved in the data storage. + /// The serialized block clone. + string Clone(IBlockEntity sourceEntity, IBlockEntity clonedEntity); + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Blocks/StoryAssetAttribute.cs b/src/Libraries/SmartStore.Services/Cms/Blocks/StoryAssetAttribute.cs new file mode 100644 index 0000000000..0cc92ccdc7 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Blocks/StoryAssetAttribute.cs @@ -0,0 +1,55 @@ +using System; + +namespace SmartStore.Services.Cms.Blocks +{ + /// + /// Specifies whether a property refers to an asset to be included in the story export. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class StoryAssetAttribute : Attribute + { + public StoryAssetAttribute(StoryAssetKind kind) + { + Kind = kind; + } + + /// + /// The asset property kind. + /// + public StoryAssetKind Kind { get; private set; } + } + + + public enum StoryAssetKind + { + /// + /// The property value is a picture identifier. + /// + Picture = 0, + + /// + /// The property value is a video path. + /// + Video, + + /// + /// The property value is a product identifier. + /// + Product, + + /// + /// The property value is a category identifier. + /// + Category, + + /// + /// The property value is a manufacturer identifier. + /// + Manufacturer, + + /// + /// The property value is a link builder expression. + /// + Link + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/ILinkResolver.cs b/src/Libraries/SmartStore.Services/Cms/ILinkResolver.cs new file mode 100644 index 0000000000..0a98489d7e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/ILinkResolver.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Cms +{ + /// + /// Provides methods to resolve link expressions. + /// + public partial interface ILinkResolver + { + /// + /// Resolves a link expression. + /// + /// + /// Link expression, e.g. product:123, category:234, topic:2, topic:aboutus etc. + /// Supported entities are product, category, manufacturer and topic. Value of topic can + /// either be the topic id or system name. + /// + /// Customer roles to check access for. null to use current customer's roles. + /// Language identifier. 0 to use current working language. + /// Store identifier. 0 to use current store. + /// LinkResolverResult + LinkResolverResult Resolve(string linkExpression, IEnumerable roles = null, int languageId = 0, int storeId = 0); + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/LinkResolver.cs b/src/Libraries/SmartStore.Services/Cms/LinkResolver.cs new file mode 100644 index 0000000000..de254b8fd4 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/LinkResolver.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Web; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Topics; +using SmartStore.Services.Localization; +using SmartStore.Services.Security; +using SmartStore.Services.Seo; +using SmartStore.Services.Stores; +using SmartStore.Services.Topics; + +namespace SmartStore.Services.Cms +{ + public partial class LinkResolver : ILinkResolver + { + /// + /// {0} : Expression w/o q + /// {1} : LanguageId + /// {2} : Store + /// {3} : RolesIdent + /// + internal const string LINKRESOLVER_KEY = "linkresolver:{0}-{1}-{2}-{3}"; + + // 0: Expression + internal const string LINKRESOLVER_PATTERN_KEY = "linkresolver:{0}-*"; + + protected readonly ICommonServices _services; + protected readonly IUrlRecordService _urlRecordService; + protected readonly ILocalizedEntityService _localizedEntityService; + protected readonly IAclService _aclService; + protected readonly IStoreMappingService _storeMappingService; + protected readonly ITopicService _topicService; + protected readonly UrlHelper _urlHelper; + + public LinkResolver( + ICommonServices services, + IUrlRecordService urlRecordService, + ILocalizedEntityService localizedEntityService, + IAclService aclService, + IStoreMappingService storeMappingService, + ITopicService topicService, + UrlHelper urlHelper) + { + _services = services; + _urlRecordService = urlRecordService; + _localizedEntityService = localizedEntityService; + _aclService = aclService; + _storeMappingService = storeMappingService; + _topicService = topicService; + _urlHelper = urlHelper; + + QuerySettings = DbQuerySettings.Default; + } + + public DbQuerySettings QuerySettings { get; set; } + + public virtual LinkResolverResult Resolve(string linkExpression, IEnumerable roles = null, int languageId = 0, int storeId = 0) + { + if (linkExpression.IsEmpty()) + { + return new LinkResolverResult { Type = LinkType.Url, Status = LinkStatus.NotFound }; + } + + if (roles == null) + { + roles = _services.WorkContext.CurrentCustomer.CustomerRoles; + } + + if (languageId == 0) + { + languageId = _services.WorkContext.WorkingLanguage.Id; + } + + if (storeId == 0) + { + storeId = _services.StoreContext.CurrentStore.Id; + } + + var d = Parse(linkExpression); + var queryString = d.QueryString; + + if (d.Type == LinkType.Url) + { + var url = d.Value.ToString(); + if (url.EmptyNull().StartsWith("~")) + { + url = VirtualPathUtility.ToAbsolute(url); + } + d.Link = d.Label = url; + } + else if (d.Type == LinkType.File) + { + d.Link = d.Label = d.Value.ToString(); + } + else + { + var cacheKey = LINKRESOLVER_KEY.FormatInvariant( + d.Expression.EmptyNull().ToLower(), + languageId, + storeId, + string.Join(",", roles.Where(x => x.Active).Select(x => x.Id))); + + d = _services.Cache.Get(cacheKey, () => + { + var d2 = d.Clone(); + + switch (d2.Type) + { + case LinkType.Product: + GetEntityData(d2, storeId, languageId, x => new ResolverEntitySummary + { + Name = x.Name, + Published = x.Published, + Deleted = x.Deleted, + SubjectToAcl = x.SubjectToAcl, + LimitedToStores = x.LimitedToStores, + PictureId = x.MainPictureId + }); + break; + case LinkType.Category: + GetEntityData(d2, storeId, languageId, x => new ResolverEntitySummary + { + Name = x.Name, + Published = x.Published, + Deleted = x.Deleted, + SubjectToAcl = x.SubjectToAcl, + LimitedToStores = x.LimitedToStores, + PictureId = x.PictureId + }); + break; + case LinkType.Manufacturer: + GetEntityData(d2, storeId, languageId, x => new ResolverEntitySummary + { + Name = x.Name, + Published = x.Published, + Deleted = x.Deleted, + LimitedToStores = x.LimitedToStores, + PictureId = x.PictureId + }); + break; + case LinkType.Topic: + GetEntityData(d2, storeId, languageId, x => null); + break; + default: + throw new SmartException("Unknown link builder type."); + } + + return d2; + }); + } + + var result = new LinkResolverResult + { + Type = d.Type, + Status = d.Status, + Value = d.Value, + Link = d.Link, + QueryString = queryString, + Label = d.Label, + Id = d.Id, + PictureId = d.PictureId + }; + + // Check ACL and limited to stores. + switch (d.Type) + { + case LinkType.Product: + case LinkType.Category: + case LinkType.Manufacturer: + case LinkType.Topic: + var entityName = d.Type.ToString(); + + if (d.CheckLimitedToStores && + d.LimitedToStores && + d.Status == LinkStatus.Ok && + !QuerySettings.IgnoreMultiStore && + !_storeMappingService.Authorize(entityName, d.Id, storeId)) + { + result.Status = LinkStatus.NotFound; + } + else if (d.SubjectToAcl && + d.Status == LinkStatus.Ok && + !QuerySettings.IgnoreAcl && + !_aclService.Authorize(entityName, d.Id, roles)) + { + result.Status = LinkStatus.Forbidden; + } + break; + } + + return result; + } + + private bool TokenizeExpression(string expression, out string type, out string path, out string query) + { + type = null; + path = null; + query = null; + + if (string.IsNullOrWhiteSpace(expression)) + { + return false; + } + + var colonIndex = expression.IndexOf(':'); + if (colonIndex > -1) + { + type = expression.Substring(0, colonIndex); + if (type.StartsWith("http", StringComparison.OrdinalIgnoreCase)) + { + type = null; + colonIndex = -1; + } + } + + path = expression.Substring(colonIndex + 1); + + var qmIndex = path.IndexOf('?'); + if (qmIndex > -1) + { + query = path.Substring(qmIndex + 1); + path = path.Substring(0, qmIndex); + } + + return true; + } + + protected virtual string GetLocalized(int entityId, string localeKeyGroup, string localeKey, int languageId, string defaultValue) + { + return _localizedEntityService.GetLocalizedValue(languageId, entityId, localeKeyGroup, localeKey).NullEmpty() ?? defaultValue.NullEmpty(); + } + + protected virtual LinkResolverData Parse(string linkExpression) + { + if (TokenizeExpression(linkExpression, out var type, out var path, out var query)) + { + if (!string.IsNullOrWhiteSpace(type) && Enum.TryParse(type, true, out LinkType linkType)) + { + var result = new LinkResolverData { Type = linkType, Expression = string.Concat(type, ":", path) }; + + switch (linkType) + { + case LinkType.Product: + case LinkType.Category: + case LinkType.Manufacturer: + case LinkType.Topic: + if (int.TryParse(path, out var id)) + { + // Reduce thrown exceptions in console + result.Value = id; + } + else + { + result.Value = path; + } + + result.QueryString = query; + break; + case LinkType.Url: + result.Value = path + (query.HasValue() ? "?" + query : ""); + break; + case LinkType.File: + result.Value = path; + result.QueryString = query; + break; + default: + throw new SmartException("Unknown link builder type."); + } + + return result; + } + } + + return new LinkResolverData { Type = LinkType.Url, Value = linkExpression.EmptyNull() }; + } + + internal void GetEntityData( + LinkResolverData data, + int storeId, + int languageId, + Expression> selector) where T : BaseEntity + { + ResolverEntitySummary summary = null; + string systemName = null; + + if (data.Value is string) + { + data.Id = 0; + systemName = (string)data.Value; + } + else + { + data.Id = (int)data.Value; + } + + if (data.Type == LinkType.Topic) + { + Topic topic = null; + + if (string.IsNullOrEmpty(systemName)) + { + topic = _services.DbContext.Set() + .AsNoTracking() + .FirstOrDefault(x => x.Id == data.Id); + } + else + { + topic = _topicService.GetTopicBySystemName(systemName, storeId, false); + data.CheckLimitedToStores = false; + } + + if (topic != null) + { + summary = new ResolverEntitySummary + { + Id = topic.Id, + Name = topic.SystemName, + Title = topic.Title, + ShortTitle = topic.ShortTitle, + Published = topic.IsPublished, + SubjectToAcl = topic.SubjectToAcl, + LimitedToStores = topic.LimitedToStores + }; + } + } + else + { + summary = _services.DbContext.Set() + .AsNoTracking() + .Where(x => x.Id == data.Id) + .Select(selector) + .FirstOrDefault(); + } + + if (summary != null) + { + var entityName = data.Type.ToString(); + + data.Id = summary.Id != 0 ? summary.Id : data.Id; + data.SubjectToAcl = summary.SubjectToAcl; + data.LimitedToStores = summary.LimitedToStores; + data.PictureId = summary.PictureId; + data.Status = summary.Deleted + ? LinkStatus.NotFound + : summary.Published ? LinkStatus.Ok : LinkStatus.Hidden; + + if (data.Type == LinkType.Topic) + { + data.Label = GetLocalized(data.Id, entityName, "ShortTitle", languageId, null) + ?? GetLocalized(data.Id, entityName, "Title", languageId, null) + ?? summary.ShortTitle.NullEmpty() + ?? summary.Title.NullEmpty() + ?? summary.Name; + } + else + { + data.Label = GetLocalized(data.Id, entityName, "Name", languageId, summary.Name); + } + + data.Slug = _urlRecordService.GetActiveSlug(data.Id, entityName, languageId).NullEmpty() ?? _urlRecordService.GetActiveSlug(data.Id, entityName, 0); + if (!string.IsNullOrEmpty(data.Slug)) + { + data.Link = _urlHelper.RouteUrl(entityName, new { SeName = data.Slug }); + } + } + else + { + data.Label = systemName; + data.Status = LinkStatus.NotFound; + } + } + } + + internal class ResolverEntitySummary + { + public int Id { get; set; } + public string Name { get; set; } + public string Title { get; set; } + public string ShortTitle { get; set; } + public bool Deleted { get; set; } + public bool Published { get; set; } + public bool SubjectToAcl { get; set; } + public bool LimitedToStores { get; set; } + public int? PictureId { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/LinkResolverInvalidator.cs b/src/Libraries/SmartStore.Services/Cms/LinkResolverInvalidator.cs new file mode 100644 index 0000000000..58cda2a320 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/LinkResolverInvalidator.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Data; +using SmartStore.Core.Data.Hooks; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Topics; +using SmartStore.Data; + +namespace SmartStore.Services.Cms +{ + public partial class LinkResolverInvalidator : DbSaveHook + { + private readonly ICommonServices _services; + + private static readonly HashSet _toxicProps = new HashSet + { + nameof(Topic.SystemName), + nameof(Topic.IsPublished), + nameof(Topic.SubjectToAcl), + nameof(Topic.LimitedToStores), + nameof(Topic.Title), + nameof(Topic.ShortTitle), + nameof(Product.Name), + nameof(Product.Deleted), + nameof(Product.MainPictureId), + nameof(Category.Published), + nameof(Category.PictureId), + nameof(StoreMapping.StoreId), + nameof(UrlRecord.IsActive), + nameof(UrlRecord.Slug) + }; + + public LinkResolverInvalidator(ICommonServices services) + { + _services = services; + } + + public override void OnBeforeSave(IHookedEntity entry) + { + var cache = _services.Cache; + var e = entry.Entity; + + var evict = e is Topic || e is Product || e is Category || e is Manufacturer || e is StoreMapping || e is UrlRecord; + if (evict && entry.InitialState == EntityState.Modified) + { + var modProps = entry.Entry.GetModifiedProperties(_services.DbContext); + evict = modProps.Keys.Any(x => _toxicProps.Contains(x)); + } + + if (!evict) + return; + + int? evictTopicId = null; + + if (e is Topic t) + { + cache.RemoveByPattern(BuildPatternKey("topic", t.Id)); + cache.RemoveByPattern(BuildPatternKey("topic", t.SystemName)); + } + else if (e is Category || e is Product || e is Manufacturer) + { + cache.RemoveByPattern(BuildPatternKey(e.GetEntityName().ToLowerInvariant(), e.Id)); + } + else if (e is UrlRecord ur) + { + cache.RemoveByPattern(BuildPatternKey(ur.EntityName.ToLowerInvariant(), ur.EntityId)); + evictTopicId = ur.EntityId; + } + else if (e is StoreMapping sm) + { + cache.RemoveByPattern(BuildPatternKey(sm.EntityName.ToLowerInvariant(), sm.EntityId)); + evictTopicId = sm.EntityId; + } + + if (evictTopicId.HasValue) + { + var systemName = _services.DbContext.Set().Where(x => x.Id == evictTopicId.Value).Select(x => x.SystemName).FirstOrDefault(); + if (systemName.HasValue()) + { + cache.RemoveByPattern(BuildPatternKey("topic", systemName)); + } + } + } + + private string BuildPatternKey(string entityName, object ident) + { + return LinkResolver.LINKRESOLVER_PATTERN_KEY.FormatInvariant(String.Concat(entityName, ":", ident)); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/LinkResolverResult.cs b/src/Libraries/SmartStore.Services/Cms/LinkResolverResult.cs new file mode 100644 index 0000000000..71002103a9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/LinkResolverResult.cs @@ -0,0 +1,145 @@ +using System; + +namespace SmartStore.Services.Cms +{ + public enum LinkType + { + Product = 0, + Category, + Manufacturer, + Topic, + Url = 20, + File = 30 + } + + public enum LinkStatus + { + Ok, + Forbidden, + NotFound, + Hidden + } + + public class LinkResolverResult + { + private string _link; + + /// + /// The raw expression without query string. + /// + public string Expression { get; set; } + + /// + /// The query string part. + /// + public string QueryString { get; set; } + + public LinkType Type { get; set; } + public object Value { get; set; } + + public LinkStatus Status { get; set; } + public string Label { get; set; } + public int Id { get; set; } + public int? PictureId { get; set; } + public string Slug { get; set; } + + public string Link + { + get + { + if (Type != LinkType.Url && !string.IsNullOrWhiteSpace(_link) && !string.IsNullOrWhiteSpace(QueryString)) + { + return string.Concat(_link, "?", QueryString); + } + + return _link; + } + set + { + _link = value; + + if (_link != null && Type != LinkType.Url) + { + var index = _link.IndexOf('?'); + if (index != -1) + { + QueryString = _link.Substring(index + 1); + _link = _link.Substring(0, index); + } + } + } + } + + public override string ToString() + { + return this.Link.EmptyNull(); + } + } + + [Serializable] + public partial class LinkResolverData : LinkResolverResult, ICloneable + { + public bool SubjectToAcl { get; set; } + public bool LimitedToStores { get; set; } + public bool CheckLimitedToStores { get; set; } = true; + + public LinkResolverData Clone() + { + return (LinkResolverData)this.MemberwiseClone(); + } + + object ICloneable.Clone() + { + return this.MemberwiseClone(); + } + } + + + public static class LinkResolverExtensions + { + public static (string Icon, string ResKey) GetLinkTypeInfo(this LinkType type) + { + switch (type) + { + case LinkType.Product: + return ("fa fa-cube", "Common.Entity.Product"); + case LinkType.Category: + return ("fa fa-sitemap", "Common.Entity.Category"); + case LinkType.Manufacturer: + return ("far fa-building", "Common.Entity.Manufacturer"); + case LinkType.Topic: + return ("far fa-file-alt", "Common.Entity.Topic"); + case LinkType.Url: + return ("fa fa-link", "Common.Url"); + case LinkType.File: + return ("far fa-folder-open", "Common.File"); + default: + throw new SmartException("Unknown link builder type."); + } + } + + /// + /// Creates the full link expression including type, value and query string. + /// + /// Whether to include the query string. + /// Link expression. + public static string CreateExpression(this LinkResolverResult data, bool includeQueryString = true) + { + if (data?.Value == null) + { + return string.Empty; + } + + var result = data.Type == LinkType.Url + ? data.Value.ToString() + : string.Concat(data.Type.ToString().ToLower(), ":", data.Value.ToString()); + + if (includeQueryString && data.Type != LinkType.Url && !string.IsNullOrWhiteSpace(data.QueryString)) + { + return string.Concat(result, "?", data.QueryString); + } + + return result; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Menus/IMenuStorage.cs b/src/Libraries/SmartStore.Services/Cms/Menus/IMenuStorage.cs new file mode 100644 index 0000000000..6218e2c7b2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Menus/IMenuStorage.cs @@ -0,0 +1,114 @@ +using System.Collections.Generic; +using SmartStore.Core; +using SmartStore.Core.Domain.Cms; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Cms +{ + public partial interface IMenuStorage + { + /// + /// Inserts a menu. + /// + /// Menu entity. + void InsertMenu(MenuRecord menu); + + /// + /// Updates a menu. + /// + /// Menu entity. + void UpdateMenu(MenuRecord menu); + + /// + /// Deletes a menu. + /// + /// Menu entity. + void DeleteMenu(MenuRecord menu); + + /// + /// Gets all menus. + /// + /// Menu system name. + /// Store identifier. 0 to get menus for all stores. + /// Whether to include hidden menus. + /// Page index. + /// Page size. + /// Menu entities. + IPagedList GetAllMenus( + string systemName = null, + int storeId = 0, + bool includeHidden = false, + int pageIndex = 0, + int pageSize = int.MaxValue); + + /// + /// Gets cached infos about all user menus. + /// + /// Customer roles to check access for. null to use current customer's roles. + /// Store identifier. 0 to use current store. + /// Menu infos. + IEnumerable GetUserMenuInfos(IEnumerable roles = null, int storeId = 0); + + /// + /// Gets a menu by identifier. + /// + /// Menu identifier. + /// Menu item entity. + MenuRecord GetMenuById(int id); + + /// + /// Checks whether the menu exists. + /// + /// Menu system name. + /// true the menu exists, false the menu doesn't exist. + bool MenuExists(string systemName); + + #region Menu items + + /// + /// Inserts a menu item. + /// + /// Menu item entity. + void InsertMenuItem(MenuItemRecord item); + + /// + /// Updates a menu item. + /// + /// Menu item entity. + void UpdateMenuItem(MenuItemRecord item); + + /// + /// Deletes a menu item. + /// + /// Menu item entity. + /// Whether to delete all child items too. + void DeleteMenuItem(MenuItemRecord item, bool deleteChilds = true); + + /// + /// Gets a menu item by identifier. + /// + /// Menu item identifier. + /// Menu item entity. + MenuItemRecord GetMenuItemById(int id); + + /// + /// Gets menu items. + /// + /// Menu item identifier. + /// Store identifier. 0 to get menus for all stores. + /// Whether to include hidden menus. + /// Menu item entities. + IList GetMenuItems(int menuId, int storeId = 0, bool includeHidden = false); + + /// + /// Gets menu items. + /// + /// Menu system name. + /// Store identifier. 0 to get menus for all stores. + /// Whether to include hidden menus. + /// Menu item entities. + IList GetMenuItems(string systemName, int storeId = 0, bool includeHidden = false); + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Menus/MenuInfo.cs b/src/Libraries/SmartStore.Services/Cms/Menus/MenuInfo.cs new file mode 100644 index 0000000000..afabc97e8b --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Menus/MenuInfo.cs @@ -0,0 +1,11 @@ +namespace SmartStore.Services.Cms +{ + public class MenuInfo + { + public int Id { get; set; } + public string SystemName { get; set; } + public string Template { get; set; } + public string[] WidgetZones { get; set; } + public int DisplayOrder { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Cms/Menus/MenuStorage.cs b/src/Libraries/SmartStore.Services/Cms/Menus/MenuStorage.cs new file mode 100644 index 0000000000..ff0332720c --- /dev/null +++ b/src/Libraries/SmartStore.Services/Cms/Menus/MenuStorage.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Data.Entity; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Caching; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Cms; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Security; +using SmartStore.Core.Domain.Stores; +using SmartStore.Data.Caching; + +namespace SmartStore.Services.Cms +{ + public partial class MenuStorage : IMenuStorage + { + private const string MENU_ALLSYSTEMNAMEs_CACHE_KEY = "MenuStorage:SystemNames"; + private const string MENU_USER_CACHE_KEY = "MenuStorage:Menus:User-{0}-{1}"; + private const string MENU_PATTERN_KEY = "MenuStorage:Menus:*"; + + private readonly IRepository _menuRepository; + private readonly IRepository _menuItemRepository; + private readonly IRepository _storeMappingRepository; + private readonly IRepository _aclRepository; + private readonly ICommonServices _services; + + public MenuStorage( + IRepository menuRepository, + IRepository menuItemRepository, + IRepository storeMappingRepository, + IRepository aclRepository, + ICommonServices services) + { + _menuRepository = menuRepository; + _menuItemRepository = menuItemRepository; + _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; + _services = services; + + QuerySettings = DbQuerySettings.Default; + } + + public DbQuerySettings QuerySettings { get; set; } + + public virtual void InsertMenu(MenuRecord menu) + { + Guard.NotNull(menu, nameof(MenuRecord)); + + menu.SystemName = menu.SystemName.ToValidPath(); + + _menuRepository.Insert(menu); + + var systemNames = GetMenuSystemNames(false); + if (systemNames != null && menu.Published) + { + systemNames.Add(menu.SystemName); + } + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + } + + public virtual void UpdateMenu(MenuRecord menu) + { + Guard.NotNull(menu, nameof(MenuRecord)); + + menu.SystemName = menu.SystemName.ToValidPath(); + + var modProps = _services.DbContext.GetModifiedProperties(menu); + + _menuRepository.Update(menu); + + var systemNames = GetMenuSystemNames(false); + if (systemNames != null) + { + if (modProps.TryGetValue(nameof(menu.Published), out var original)) + { + if (original.Convert() == true) + { + systemNames.Remove(menu.SystemName); + } + else + { + systemNames.Add(menu.SystemName); + } + } + else if (modProps.TryGetValue(nameof(menu.SystemName), out original)) + { + systemNames.Remove((string)original); + systemNames.Add(menu.SystemName); + } + } + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + } + + public virtual void DeleteMenu(MenuRecord menu) + { + if (menu == null) + return; + + _menuRepository.Delete(menu); + + var systemNames = GetMenuSystemNames(false); + if (systemNames != null) + { + systemNames.Remove(menu.SystemName); + } + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + } + + public virtual IPagedList GetAllMenus( + string systemName = null, + int storeId = 0, + bool includeHidden = false, + int pageIndex = 0, + int pageSize = int.MaxValue) + { + var query = BuildMenuQuery(0, storeId, systemName, null, includeHidden); + return new PagedList(query, pageIndex, pageSize); + } + + public virtual IEnumerable GetUserMenuInfos(IEnumerable roles = null, int storeId = 0) + { + if (roles == null) + { + roles = _services.WorkContext.CurrentCustomer.CustomerRoles; + } + + if (storeId == 0) + { + storeId = _services.StoreContext.CurrentStore.Id; + } + + var cacheKey = MENU_USER_CACHE_KEY.FormatInvariant(storeId, string.Join(",", roles.Where(x => x.Active).Select(x => x.Id))); + + var userMenusInfo = _services.Cache.Get(cacheKey, () => + { + var query = BuildMenuQuery(0, storeId, null, false, false, true, false, true); + + var data = query.Select(x => new + { + x.Id, + x.SystemName, + x.Template, + x.WidgetZone, + x.DisplayOrder + }) + .ToList(); + + var result = data.Select(x => new MenuInfo + { + Id = x.Id, + SystemName = x.SystemName, + Template = x.Template, + DisplayOrder = x.DisplayOrder, + WidgetZones = x.WidgetZone.EmptyNull().Trim() + .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(y => y.Trim()) + .ToArray() + }) + .ToList(); + + return result; + }); + + return userMenusInfo; + } + + public virtual MenuRecord GetMenuById(int id) + { + if (id == 0) + { + return null; + } + + return _menuRepository.GetByIdCached(id, "db.menurecord.id-" + id); + } + + public virtual bool MenuExists(string systemName) + { + if (systemName.IsEmpty()) + { + return false; + } + + return GetMenuSystemNames(true).Contains(systemName); + } + + #region Menu items + + public virtual void InsertMenuItem(MenuItemRecord item) + { + Guard.NotNull(item, nameof(MenuRecord)); + + // Prevent inconsistent tree structure. + if (item.ParentItemId != 0 && item.ParentItemId == item.Id) + { + item.ParentItemId = 0; + } + + _menuItemRepository.Insert(item); + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + } + + public virtual void UpdateMenuItem(MenuItemRecord item) + { + Guard.NotNull(item, nameof(MenuRecord)); + + // Prevent inconsistent tree structure. + if (item.ParentItemId != 0 && item.ParentItemId == item.Id) + { + item.ParentItemId = 0; + } + + _menuItemRepository.Update(item); + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + } + + public virtual void DeleteMenuItem(MenuItemRecord item, bool deleteChilds = true) + { + if (item == null) + { + return; + } + + if (!deleteChilds) + { + _menuItemRepository.Delete(item); + } + else + { + var ids = new HashSet { item.Id }; + GetChildIds(item.Id, ids); + + foreach (var chunk in ids.Slice(200)) + { + var items = _menuItemRepository.Table.Where(x => chunk.Contains(x.Id)).ToList(); + _menuItemRepository.DeleteRange(items); + } + } + + _services.Cache.RemoveByPattern(MENU_PATTERN_KEY); + + void GetChildIds(int parentId, HashSet ids) + { + var childIds = _menuItemRepository.TableUntracked + .Where(x => x.ParentItemId == parentId) + .Select(x => x.Id) + .ToList(); + + if (childIds.Any()) + { + ids.AddRange(childIds); + childIds.Each(x => GetChildIds(x, ids)); + } + } + } + + public virtual MenuItemRecord GetMenuItemById(int id) + { + if (id == 0) + { + return null; + } + + return _menuItemRepository.GetByIdCached(id, "db.menuitemrecord.id-" + id); + } + + public virtual IList GetMenuItems(int menuId, int storeId = 0, bool includeHidden = false) + { + if (menuId == 0) + { + return new List(); + } + + var query = BuildMenuItemQuery(menuId, null, storeId, includeHidden); + return query.ToList(); + } + + public virtual IList GetMenuItems(string systemName, int storeId = 0, bool includeHidden = false) + { + if (systemName.IsEmpty()) + { + return new List(); + } + + var query = BuildMenuItemQuery(0, systemName, storeId, includeHidden); + return query.ToList(); + } + + #endregion + + #region Utilities + + private ISet GetMenuSystemNames(bool create) + { + if (create || _services.Cache.Contains(MENU_ALLSYSTEMNAMEs_CACHE_KEY)) + { + return _services.Cache.GetHashSet(MENU_ALLSYSTEMNAMEs_CACHE_KEY, () => + { + return _menuRepository.TableUntracked + .Where(x => x.Published) + .Select(x => x.SystemName) + .ToArray(); + }); + } + + return null; + } + + protected virtual IQueryable BuildMenuQuery( + int id, + int storeId, + string systemName, + bool? isSystemMenu, + bool includeHidden, + bool groupBy = true, + bool sort = true, + bool untracked = false) + { + var applied = false; + var entityName = nameof(MenuRecord); + var query = untracked ? _menuRepository.TableUntracked : _menuRepository.Table; + + query = query.Where(x => includeHidden || x.Published); + + if (id != 0) + { + query = query.Where(x => x.Id == id); + } + + if (systemName.HasValue()) + { + query = query.Where(x => x.SystemName == systemName); + } + + if (isSystemMenu.HasValue) + { + query = query.Where(x => x.IsSystemMenu == isSystemMenu.Value); + } + + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join m in _storeMappingRepository.Table + on new { x1 = x.Id, x2 = entityName } equals new { x1 = m.EntityId, x2 = m.EntityName } into sm + from m in sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == m.StoreId + select x; + + applied = true; + } + + if (!includeHidden && !QuerySettings.IgnoreAcl) + { + var allowedRoleIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + + query = + from x in query + join a in _aclRepository.Table + on new { x1 = x.Id, x2 = entityName } equals new { x1 = a.EntityId, x2 = a.EntityName } into ac + from a in ac.DefaultIfEmpty() + where !x.SubjectToAcl || allowedRoleIds.Contains(a.CustomerRoleId) + select x; + + applied = true; + } + + if (applied && groupBy) + { + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + + if (sort) + { + query = query.OrderBy(x => x.SystemName).ThenBy(x => x.Title); + } + + return query; + } + + protected virtual IQueryable BuildMenuItemQuery( + int menuId, + string systemName, + int storeId, + bool includeHidden) + { + var singleMenu = menuId != 0 || (systemName.HasValue() && storeId != 0); + var menuQuery = BuildMenuQuery(menuId, storeId, systemName, null, includeHidden, !singleMenu, !singleMenu); + + if (singleMenu) + { + menuQuery = menuQuery.Take(1); + } + + var query = + from m in menuQuery + join mi in _menuItemRepository.Table on m.Id equals mi.MenuId + where includeHidden || mi.Published + orderby mi.ParentItemId, mi.DisplayOrder + select mi; + + return query; + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs index 4c0e1c05d0..47f7a8edf1 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeExtentions.cs @@ -9,85 +9,41 @@ namespace SmartStore.Services.Common public static class GenericAttributeExtentions { /// - /// Get an attribute of an entity + /// Gets an entity generic attribute value /// - /// Property type + /// Property type /// Entity /// Key /// Load a value specific for a certain store; pass 0 to load a value shared for all stores - /// Attribute - public static TPropType GetAttribute(this BaseEntity entity, string key, int storeId = 0) + /// Attribute value + public static TProp GetAttribute(this BaseEntity entity, string key, int storeId = 0) { - var genericAttributeService = EngineContext.Current.Resolve(); - return GetAttribute(entity, key, genericAttributeService, storeId); + return GetAttribute( + entity, + key, + EngineContext.Current.Resolve(), + storeId); } - /// - /// Get an attribute of an entity - /// - /// Property type - /// Entity - /// Key - /// GenericAttributeService - /// Load a value specific for a certain store; pass 0 to load a value shared for all stores - /// Attribute - public static TPropType GetAttribute(this BaseEntity entity, string key, IGenericAttributeService genericAttributeService, int storeId = 0) - { - if (entity == null) - throw new ArgumentNullException("entity"); - - if (genericAttributeService == null) - genericAttributeService = EngineContext.Current.Resolve(); - - string keyGroup = entity.GetUnproxiedType().Name; - - return genericAttributeService.GetAttribute(keyGroup, entity.Id, key, storeId); - - #region Old - //var props = genericAttributeService.GetAttributesForEntity(entity.Id, keyGroup); - ////little hack here (only for unit testing). we should write expect-return rules in unit tests for such cases - //if (props == null) - // return default(TPropType); - //props = props.Where(x => x.StoreId == storeId).ToList(); - //if (props.Count == 0) - // return default(TPropType); - - //var prop = props.FirstOrDefault(ga => - // ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); //should be culture invariant - //if (prop == null || string.IsNullOrEmpty(prop.Value)) - // return default(TPropType); - - //return CommonHelper.To(prop.Value); - #endregion - } - - public static TPropType GetAttribute(this IGenericAttributeService genericAttributeService, string entityName, int entityId, string key, int storeId = 0) + /// + /// Gets an entity generic attribute value + /// + /// Property type + /// Entity + /// Key + /// GenericAttributeService + /// Load a value specific for a certain store; pass 0 to load a value shared for all stores + /// Attribute value + public static TProp GetAttribute(this BaseEntity entity, string key, IGenericAttributeService genericAttributeService, int storeId = 0) { - Guard.NotNull(genericAttributeService, nameof(genericAttributeService)); - Guard.NotEmpty(entityName, nameof(entityName)); - - var props = 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) - { - return default(TPropType); - } - - if (!props.Any(x => x.StoreId == storeId)) - { - return default(TPropType); - } - - var prop = props.FirstOrDefault(ga => - ga.StoreId == storeId && ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); //should be culture invariant - - if (prop == null || prop.Value.IsEmpty()) - { - return default(TPropType); - } - - return prop.Value.Convert(); - } + if (entity == null) + throw new ArgumentNullException(nameof(entity)); + + return (genericAttributeService ?? EngineContext.Current.Resolve()).GetAttribute( + entity.GetUnproxiedType().Name, + entity.Id, + key, + storeId); + } } } diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs index 5c6a353ddb..3fb471d1d0 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; @@ -15,24 +14,25 @@ namespace SmartStore.Services.Common { public partial class GenericAttributeService : IGenericAttributeService { - private readonly IRepository _genericAttributeRepository; + private readonly IRepository _genericAttributeRepository; private readonly IEventPublisher _eventPublisher; private readonly IRepository _orderRepository; + private readonly IDictionary> _valueCache; - public GenericAttributeService( + public GenericAttributeService( IRepository genericAttributeRepository, IEventPublisher eventPublisher, IRepository orderRepository) { - this._genericAttributeRepository = genericAttributeRepository; - this._eventPublisher = eventPublisher; - this._orderRepository = orderRepository; - } + _genericAttributeRepository = genericAttributeRepository; + _eventPublisher = eventPublisher; + _orderRepository = orderRepository; + _valueCache = new Dictionary>(); + } 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 +57,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 +70,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) { @@ -83,23 +81,21 @@ public virtual void UpdateAttribute(GenericAttribute attribute) } } - public virtual IList GetAttributesForEntity(int entityId, string keyGroup) + public virtual IList GetAttributesForEntity(int entityId, string entityName) { var query = from ga in _genericAttributeRepository.Table - where ga.EntityId == entityId && - ga.KeyGroup == keyGroup + where ga.EntityId == entityId && ga.KeyGroup == entityName select ga; - var attributes = query.ToListCached("db.ga.{0}-{1}".FormatInvariant(entityId, keyGroup)); - return attributes; + return query.ToListCached("db.ga.{0}-{1}".FormatInvariant(entityId, entityName)); } - public virtual Multimap GetAttributesForEntity(int[] entityIds, string keyGroup) + public virtual Multimap GetAttributesForEntity(int[] entityIds, string entityName) { Guard.NotNull(entityIds, nameof(entityIds)); var query = _genericAttributeRepository.TableUntracked - .Where(x => entityIds.Contains(x.EntityId) && x.KeyGroup == keyGroup); + .Where(x => entityIds.Contains(x.EntityId) && x.KeyGroup == entityName); var map = query .ToList() @@ -108,29 +104,71 @@ public virtual Multimap GetAttributesForEntity(int[] enti return map; } - public virtual IQueryable GetAttributes(string key, string keyGroup) + public virtual TProp GetAttribute(string entityName, int entityId, string key, int storeId = 0) + { + if (entityName == null) + throw new ArgumentNullException(nameof(entityName)); + + if (key == null) + throw new ArgumentNullException(nameof(key)); + + var entityKey = entityName.ToLowerInvariant() + entityId; + if (!_valueCache.TryGetValue(entityKey, out var attrs)) + { + var list = GetAttributesForEntity(entityId, entityName); + if (list.Count > 0) + { + attrs = list.ToDictionarySafe(k => k.Key.ToLowerInvariant() + k.StoreId, v => (object)v.Value); + } + _valueCache[entityKey] = attrs; + } + + TProp result = default; + + if (attrs != null) + { + var entryKey = key.ToLowerInvariant() + storeId; + if (attrs.TryGetValue(entryKey, out var rawValue)) + { + if (rawValue.GetType() == typeof(TProp)) + { + result = (TProp)rawValue; + } + else + { + result = rawValue.Convert(); + // to skip repeated value type conversion + attrs[entryKey] = result; + } + } + } + + return result; + } + + public virtual IQueryable GetAttributes(string key, string entityName) { var query = from ga in _genericAttributeRepository.Table - where ga.Key == key && ga.KeyGroup == keyGroup + where ga.Key == key && ga.KeyGroup == entityName select ga; return query; } - public virtual void SaveAttribute(BaseEntity entity, string key, TPropType value, int storeId = 0) + public virtual void SaveAttribute(BaseEntity entity, string key, TProp value, int storeId = 0) { Guard.NotNull(entity, nameof(entity)); SaveAttribute(entity.Id, key, entity.GetUnproxiedType().Name, value, storeId); } - public virtual void SaveAttribute(int entityId, string key, string keyGroup, TPropType value, int storeId = 0) + public virtual void SaveAttribute(int entityId, string key, string entityName, TProp value, int storeId = 0) { Guard.NotZero(entityId, nameof(entityId)); var valueStr = value.Convert(); - var props = GetAttributesForEntity(entityId, keyGroup); + var props = GetAttributesForEntity(entityId, entityName); // should be culture invariant var prop = props.FirstOrDefault(ga => ga.StoreId == storeId && ga.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)); @@ -141,12 +179,14 @@ public virtual void SaveAttribute(int entityId, string key, string ke { // delete DeleteAttribute(prop); + CacheTryRemove(entityName, entityId, prop); } else { // update prop.Value = valueStr; UpdateAttribute(prop); + CacheTryAddOrUpdate(entityName, entityId, prop); } } else @@ -158,13 +198,30 @@ public virtual void SaveAttribute(int entityId, string key, string ke { EntityId = entityId, Key = key, - KeyGroup = keyGroup, + KeyGroup = entityName, Value = valueStr, StoreId = storeId }; + InsertAttribute(prop); + + CacheTryAddOrUpdate(entityName, entityId, prop); } } } + + private void CacheTryRemove(string entityName, int entityId, GenericAttribute attr) + { + var entityKey = entityName.ToLowerInvariant() + entityId; + var entryKey = attr.Key.ToLowerInvariant() + attr.StoreId; + _valueCache.Get(entityKey).TryRemove(entryKey, out _); + } + + private void CacheTryAddOrUpdate(string entityName, int entityId, GenericAttribute attr) + { + var entityKey = entityName.ToLowerInvariant() + entityId; + var entryKey = attr.Key.ToLowerInvariant() + attr.StoreId; + _valueCache.Get(entityKey).TryAdd(entryKey, attr.Value, true); + } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs index c584c4ad4c..dffd998afe 100644 --- a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs @@ -40,45 +40,55 @@ public partial interface IGenericAttributeService /// Get attributes /// /// Entity identifier - /// Key group + /// Key group /// Generic attributes - IList GetAttributesForEntity(int entityId, string keyGroup); + IList GetAttributesForEntity(int entityId, string entityName); /// /// Get attributes /// /// Entity identifiers - /// Key group + /// Key group /// Generic attributes - Multimap GetAttributesForEntity(int[] entityIds, string keyGroup); + Multimap GetAttributesForEntity(int[] entityIds, string entityName); + + /// + /// Get attribute value + /// + /// Key group + /// Entity identifier + /// Key + /// Store identifier; pass 0 if this attribute will be available for all stores + /// Converted generic attribute value + TProp GetAttribute(string entityName, int entityId, string key, int storeId = 0); /// /// Get queryable attributes /// /// The key - /// The key group + /// The key group /// Queryable attributes - IQueryable GetAttributes(string key, string keyGroup); + IQueryable GetAttributes(string key, string entityName); /// /// Save attribute value /// - /// Property type + /// Property type /// Entity /// Key /// Value /// Store identifier; pass 0 if this attribute will be available for all stores - void SaveAttribute(BaseEntity entity, string key, TPropType value, int storeId = 0); + void SaveAttribute(BaseEntity entity, string key, TProp value, int storeId = 0); /// /// Save attribute value /// - /// Property type + /// Property type /// Entity identifier /// The key /// The key group - /// Property type + /// Property type /// Store identifier; pass 0 if this attribute will be available for all stores - void SaveAttribute(int entityId, string key, string keyGroup, TPropType value, int storeId = 0); + void SaveAttribute(int entityId, string key, string keyGroup, TProp value, int storeId = 0); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Common/MaintenanceService.cs b/src/Libraries/SmartStore.Services/Common/MaintenanceService.cs index 069731f52a..d5b3c8d746 100644 --- a/src/Libraries/SmartStore.Services/Common/MaintenanceService.cs +++ b/src/Libraries/SmartStore.Services/Common/MaintenanceService.cs @@ -50,7 +50,7 @@ public MaintenanceService(IDataProvider dataProvider, IDbContext dbContext, { //stored procedures are enabled and supported by the database. - //TODO: find a better way to get table name + // TODO: find a better way to get table name var tableName = typeof(T).Name; var result = _dbContext.SqlQuery(string.Format("SELECT IDENT_CURRENT('[{0}]')", tableName)); return Convert.ToInt32(result.FirstOrDefault()); diff --git a/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs b/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs index a07dfe8cce..e02429fba3 100644 --- a/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs +++ b/src/Libraries/SmartStore.Services/Common/TempFileCleanupTask.cs @@ -10,7 +10,7 @@ public partial class TempFileCleanupTask : ITask { public void Execute(TaskExecutionContext ctx) { - FileSystemHelper.TempCleanup(); + FileSystemHelper.ClearTempDirectories(); } } } diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index e6a92fc481..1caeae24d0 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; @@ -60,7 +57,24 @@ protected virtual IDictionary GetAllCachedSettings() } return dictionary; - }); + }, independent: true); + } + + 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) @@ -138,7 +152,7 @@ public virtual Setting GetSettingById(int settingId) public virtual T GetSettingByKey( string key, - T defaultValue = default(T), + T defaultValue = default, int storeId = 0, bool loadSharedValueIfNotFound = false) { @@ -148,9 +162,7 @@ public virtual T GetSettingByKey( var cacheKey = CreateCacheKey(key, storeId); - CachedSetting cachedSetting; - - if (settings.TryGetValue(cacheKey, out cachedSetting)) + if (settings.TryGetValue(cacheKey, out CachedSetting cachedSetting)) { return cachedSetting.Value.Convert(); } @@ -183,23 +195,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; @@ -225,32 +222,33 @@ public ISettings LoadSetting(Type settingType, int storeId = 0) protected virtual ISettings LoadSettingCore(Type settingType, int storeId = 0) { - if (settingType.HasAttribute(true)) - { - return LoadSettingsJson(settingType, storeId); - } - - var settings = (ISettings)Activator.CreateInstance(settingType); - + var allSettings = GetAllCachedSettings(); + var instance = (ISettings)Activator.CreateInstance(settingType); var prefix = settingType.Name; foreach (var fastProp in FastProperty.GetProperties(settingType).Values) { var prop = fastProp.Property; - // get properties we can read and write to + // Get properties we can read and write to if (!prop.CanWrite) continue; - var key = prefix + "." + prop.Name; - // load by store - string setting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true); + string key = prefix + "." + prop.Name; + + if (!allSettings.TryGetValue(CreateCacheKey(key, storeId), out var cachedSetting) && storeId > 0) + { + // // Fallback to shared (storeId = 0) + allSettings.TryGetValue(CreateCacheKey(key, 0), out cachedSetting); + } + string setting = cachedSetting?.Value; + if (setting == null) { if (fastProp.IsSequenceType) { - if ((fastProp.GetValue(settings) as IEnumerable) != null) + if ((fastProp.GetValue(instance) as IEnumerable) != null) { // Instance of IEnumerable<> was already created, most likely in the constructor of the settings concrete class. // In this case we shouldn't let the EnumerableConverter create a new instance but keep this one. @@ -295,7 +293,7 @@ protected virtual ISettings LoadSettingCore(Type settingType, int storeId = 0) object value = converter.ConvertFrom(setting); // Set property - fastProp.SetValue(settings, value); + fastProp.SetValue(instance, value); } catch (Exception ex) { @@ -304,7 +302,7 @@ protected virtual ISettings LoadSettingCore(Type settingType, int storeId = 0) } } - return settings; + return instance; } public virtual void SetSetting(string key, T value, int storeId = 0, bool clearCache = true) @@ -314,10 +312,9 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle var str = value.Convert(); var allSettings = GetAllCachedSettings(); var cacheKey = CreateCacheKey(key, storeId); - CachedSetting cachedSetting; var insert = false; - if (allSettings.TryGetValue(cacheKey, out cachedSetting)) + if (allSettings.TryGetValue(cacheKey, out CachedSetting cachedSetting)) { var setting = GetSettingById(cachedSetting.Id); if (setting != null) @@ -371,12 +368,6 @@ protected virtual void SaveSettingCore(ISettings settings, int storeId = 0) var settingType = settings.GetType(); var prefix = settingType.Name; - if (settingType.HasAttribute(true)) - { - //SaveSettingsJson(settings); - //return; - } - /* We do not clear cache after each setting update. * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ @@ -405,24 +396,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 = member.Member as PropertyInfo; - if (propInfo == null) - { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } + var propInfo = GetPropertyInfo(keySelector); + var key = string.Concat(typeof(T).Name, ".", propInfo.Name); - 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 +413,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) @@ -457,12 +438,6 @@ public virtual void DeleteSetting(Setting setting) { using (BeginScope()) { - if (typeof(T).HasAttribute(true)) - { - DeleteSettingsJson(); - return; - } - var settingsToDelete = new List(); var allSettings = GetAllSettings(); foreach (var prop in typeof(T).GetProperties()) @@ -483,23 +458,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/CustomerRegistrationService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs index 81114192cb..6343755061 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs @@ -25,13 +25,13 @@ public CustomerRegistrationService(ICustomerService customerService, RewardPointsSettings rewardPointsSettings, CustomerSettings customerSettings, IStoreContext storeContext, IEventPublisher eventPublisher) { - this._customerService = customerService; - this._encryptionService = encryptionService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._rewardPointsSettings = rewardPointsSettings; - this._customerSettings = customerSettings; - this._storeContext = storeContext; - this._eventPublisher = eventPublisher; + _customerService = customerService; + _encryptionService = encryptionService; + _newsLetterSubscriptionService = newsLetterSubscriptionService; + _rewardPointsSettings = rewardPointsSettings; + _customerSettings = customerSettings; + _storeContext = storeContext; + _eventPublisher = eventPublisher; T = NullLocalizer.Instance; } @@ -42,10 +42,18 @@ public virtual bool ValidateCustomer(string usernameOrEmail, string password) { Customer customer = null; - if (_customerSettings.UsernamesEnabled) + if (_customerSettings.CustomerLoginType == CustomerLoginType.Email) + { + customer = _customerService.GetCustomerByEmail(usernameOrEmail); + } + else if (_customerSettings.CustomerLoginType == CustomerLoginType.Username) + { customer = _customerService.GetCustomerByUsername(usernameOrEmail); + } else - customer = _customerService.GetCustomerByEmail(usernameOrEmail); + { + customer = _customerService.GetCustomerByEmail(usernameOrEmail) ?? _customerService.GetCustomerByUsername(usernameOrEmail); + } if (customer == null || customer.Deleted || !customer.Active) return false; @@ -110,7 +118,7 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR return result; } - if (String.IsNullOrEmpty(request.Email)) + if (!request.Email.HasValue()) { result.AddError(T("Account.Register.Errors.EmailIsNotProvided")); return result; @@ -122,19 +130,16 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR return result; } - if (String.IsNullOrWhiteSpace(request.Password)) + if (!request.Password.HasValue()) { result.AddError(T("Account.Register.Errors.PasswordIsNotProvided")); return result; } - if (_customerSettings.UsernamesEnabled) + if (_customerSettings.CustomerLoginType != CustomerLoginType.Email && !request.Username.HasValue()) { - if (String.IsNullOrEmpty(request.Username)) - { - result.AddError(T("Account.Register.Errors.UsernameIsNotProvided")); - return result; - } + result.AddError(T("Account.Register.Errors.UsernameIsNotProvided")); + return result; } // Validate unique user @@ -144,13 +149,10 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR return result; } - if (_customerSettings.UsernamesEnabled) + if (_customerSettings.CustomerLoginType != CustomerLoginType.Email && _customerService.GetCustomerByUsername(request.Username) != null) { - if (_customerService.GetCustomerByUsername(request.Username) != null) - { - result.AddError(T("Account.Register.Errors.UsernameAlreadyExists")); - return result; - } + result.AddError(T("Account.Register.Errors.UsernameAlreadyExists")); + return result; } // At this point request is valid @@ -214,12 +216,12 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request Guard.NotNull(request, nameof(request)); var result = new PasswordChangeResult(); - if (String.IsNullOrWhiteSpace(request.Email)) + if (!request.Email.HasValue()) { result.AddError(T("Account.ChangePassword.Errors.EmailIsNotProvided")); return result; } - if (String.IsNullOrWhiteSpace(request.NewPassword)) + if (!request.NewPassword.HasValue()) { result.AddError(T("Account.ChangePassword.Errors.PasswordIsNotProvided")); return result; @@ -232,7 +234,6 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request return result; } - var requestIsValid = false; if (request.ValidateRequest) { @@ -307,15 +308,15 @@ public virtual void SetEmail(Customer customer, string newEmail) if (newEmail.Length > 100) throw new SmartException(T("Account.EmailUsernameErrors.EmailTooLong")); - var customer2 = _customerService.GetCustomerByEmail(newEmail); - if (customer2 != null && customer.Id != customer2.Id) + var newCustomer = _customerService.GetCustomerByEmail(newEmail); + if (newCustomer != null && customer.Id != newCustomer.Id) throw new SmartException(T("Account.EmailUsernameErrors.EmailAlreadyExists")); customer.Email = newEmail; _customerService.UpdateCustomer(customer); //update newsletter subscription (if required) - if (!String.IsNullOrEmpty(oldEmail) && !oldEmail.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase)) + if (oldEmail.HasValue() && !oldEmail.Equals(newEmail, StringComparison.InvariantCultureIgnoreCase)) { var subscriptionOld = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(oldEmail, _storeContext.CurrentStore.Id); if (subscriptionOld != null) @@ -330,7 +331,7 @@ public virtual void SetUsername(Customer customer, string newUsername) { Guard.NotNull(customer, nameof(customer)); - if (!_customerSettings.UsernamesEnabled) + if (_customerSettings.CustomerLoginType == CustomerLoginType.Email) throw new SmartException("Usernames are disabled"); if (!_customerSettings.AllowUsersToChangeUsernames) @@ -341,8 +342,8 @@ public virtual void SetUsername(Customer customer, string newUsername) if (newUsername.Length > 100) throw new SmartException(T("Account.EmailUsernameErrors.UsernameTooLong")); - var user2 = _customerService.GetCustomerByUsername(newUsername); - if (user2 != null && customer.Id != user2.Id) + var newUser = _customerService.GetCustomerByUsername(newUsername); + if (newUser != null && customer.Id != newUser.Id) throw new SmartException(T("Account.EmailUsernameErrors.UsernameAlreadyExists")); customer.Username = newUsername; diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerReportService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerReportService.cs index abd95dd74d..6209807af8 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerReportService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerReportService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Data.Entity; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; @@ -106,8 +107,8 @@ group co by co.c.Id into g throw new ArgumentException("Wrong orderBy parameter", "orderBy"); } - //load 20 customers - query2 = query2.Take(20); + // load 20 customers + query2 = query2.Take(() => 20); var result = query2.ToList().Select(x => { diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs new file mode 100644 index 0000000000..e0737ac075 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/CustomerSearchQuery.cs @@ -0,0 +1,124 @@ +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 active customers should be loaded. + /// + public bool? Active { get; set; } + + /// + /// 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..b6890718c9 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Web; +using System.Data.Entity; 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,48 +14,57 @@ 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.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Data.Caching; using SmartStore.Services.Common; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; +using System.Threading.Tasks; namespace SmartStore.Services.Customers { - public partial class CustomerService : ICustomerService + public partial class CustomerService : ICustomerService { private readonly IRepository _customerRepository; private readonly IRepository _customerRoleRepository; private readonly IRepository _gaRepository; private readonly IRepository _rewardPointsHistoryRepository; + private readonly IRepository _shoppingCartItemRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly RewardPointsSettings _rewardPointsSettings; private readonly ICommonServices _services; private readonly HttpContextBase _httpContext; private readonly IUserAgent _userAgent; + private readonly CustomerSettings _customerSettings; + private readonly Lazy _gdprTool; public CustomerService( IRepository customerRepository, IRepository customerRoleRepository, IRepository gaRepository, IRepository rewardPointsHistoryRepository, + IRepository shoppingCartItemRepository, IGenericAttributeService genericAttributeService, RewardPointsSettings rewardPointsSettings, ICommonServices services, HttpContextBase httpContext, - IUserAgent userAgent) + IUserAgent userAgent, + CustomerSettings customerSettings, + 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; + _shoppingCartItemRepository = shoppingCartItemRepository; + _genericAttributeService = genericAttributeService; + _rewardPointsSettings = rewardPointsSettings; + _services = services; + _httpContext = httpContext; + _userAgent = userAgent; + _customerSettings = customerSettings; + _gdprTool = gdprTool; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -68,196 +76,220 @@ 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)) + public virtual IPagedList SearchCustomers(CustomerSearchQuery q) + { + Guard.NotNull(q, nameof(q)); + + var isOrdered = false; + IQueryable query = null; + + if (q.OnlyWithCart) { - 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); + var cartItemQuery = _shoppingCartItemRepository.TableUntracked.Expand(x => x.Customer); + + if (q.CartType.HasValue) + { + cartItemQuery = cartItemQuery.Where(x => x.ShoppingCartTypeId == (int)q.CartType.Value); + } + + var groupQuery = + from sci in cartItemQuery + group sci by sci.CustomerId into grp + select grp + .OrderByDescending(x => x.CreatedOnUtc) + .Select(x => new + { + x.Customer, + x.CreatedOnUtc + }) + .FirstOrDefault(); + + // We have to sort again because of paging. + query = groupQuery + .OrderByDescending(x => x.CreatedOnUtc) + .Select(x => x.Customer); + + isOrdered = true; } - //search by phone - if (!String.IsNullOrWhiteSpace(phone)) + else { - 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); + query = _customerRepository.Table; } - //search by zip - if (!String.IsNullOrWhiteSpace(zipPostalCode)) + + if (q.Email.HasValue()) + { + query = query.Where(c => c.Email.Contains(q.Email)); + } + + if (q.Username.HasValue()) + { + query = query.Where(c => c.Username.Contains(q.Username)); + } + + if (q.CustomerNumber.HasValue()) + { + query = query.Where(c => c.CustomerNumber.Contains(q.CustomerNumber)); + } + + 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.Active.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(zipPostalCode))) - .Select(z => z.Customer); + query = query.Where(c => c.Active == q.Active.Value); } - if (loadOnlyWithShoppingCart) - { - int? sctId = null; - if (sct.HasValue) - sctId = (int)sct.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); + } - query = sct.HasValue ? - query.Where(c => c.ShoppingCartItems.Where(x => x.ShoppingCartTypeId == sctId).Count() > 0) : - query.Where(c => c.ShoppingCartItems.Count() > 0); + if (!isOrdered) + { + query = query.OrderByDescending(c => c.CreatedOnUtc); } - - query = query.OrderByDescending(c => c.CreatedOnUtc); - var customers = new PagedList(query, pageIndex, pageSize); - return customers; - } + var customers = new PagedList(query, q.PageIndex, q.PageSize); + return customers; + } - public virtual IPagedList GetAllCustomers(int affiliateId, int pageIndex, int pageSize) + public virtual IPagedList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat) { - var query = _customerRepository.Table; - query = query.Where(c => !c.Deleted); - query = query.Where(c => c.AffiliateId == affiliateId); - query = query.OrderByDescending(c => c.CreatedOnUtc); + var q = new CustomerSearchQuery + { + PasswordFormat = passwordFormat, + PageIndex = 0, + PageSize = 500 + }; - var customers = new PagedList(query, pageIndex, pageSize); - return customers; + var customers = SearchCustomers(q); + return customers; } - public virtual IList GetAllCustomersByPasswordFormat(PasswordFormat passwordFormat) + public virtual IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, int[] customerRoleIds, int pageIndex, int pageSize) { - int passwordFormatId = (int)passwordFormat; + var q = new CustomerSearchQuery + { + LastActivityFromUtc = lastActivityFromUtc, + CustomerRoleIds = customerRoleIds, + IsSystemAccount = false, + PageIndex = pageIndex, + PageSize = pageSize + }; - 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); + + customers.AlterQuery(x => x.OrderByDescending(c => c.LastActivityDateUtc)); - 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); 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 +353,7 @@ public virtual Customer GetCustomerByEmail(string email) orderby c.Id where c.Email == email select c; + var customer = query.FirstOrDefault(); return customer; } @@ -334,6 +367,7 @@ public virtual Customer GetCustomerBySystemName(string systemName) orderby c.Id where c.SystemName == systemName select c; + var customer = query.FirstOrDefault(); return customer; } @@ -429,62 +463,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); @@ -493,7 +529,11 @@ public virtual void ResetCheckoutData(Customer customer, int storeId, UpdateCustomer(customer); } - public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? registrationTo, bool onlyWithoutShoppingCart, int maxItemsToDelete = 5000) + public virtual async Task DeleteGuestCustomersAsync( + DateTime? registrationFrom, + DateTime? registrationTo, + bool onlyWithoutShoppingCart, + int maxItemsToDelete = 5000) { var ctx = _customerRepository.Context; @@ -530,7 +570,7 @@ public virtual int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? re // no forum topics query = JoinWith(query, x => x.CustomerId); - //don't delete system accounts + // don't delete system accounts query = query.Where(c => !c.IsSystemAccount); // only distinct items @@ -541,7 +581,7 @@ orderby cGroup.Key select cGroup.FirstOrDefault(); query = query.OrderBy(c => c.Id); - var customers = query.Take(maxItemsToDelete).ToList(); + var customers = await query.Take(() => maxItemsToDelete).ToListAsync(); int numberOfDeletedCustomers = 0; foreach (var c in customers) @@ -550,10 +590,9 @@ orderby cGroup.Key { // delete attributes (using GenericAttributeService would incorporate caching... which is bad in long running processes) var gaQuery = from ga in _gaRepository.Table - where ga.EntityId == c.Id && - ga.KeyGroup == "Customer" + where ga.EntityId == c.Id && ga.KeyGroup == "Customer" select ga; - var attributes = gaQuery.ToList(); + var attributes = await gaQuery.ToListAsync(); _gaRepository.DeleteRange(attributes); @@ -566,7 +605,7 @@ orderby cGroup.Key // save changes all 1000th item try { - scope.Commit(); + await ctx.SaveChangesAsync(); } catch (Exception ex) { @@ -581,7 +620,7 @@ orderby cGroup.Key } // save the rest - scope.Commit(); + await ctx.SaveChangesAsync(); return numberOfDeletedCustomers; } @@ -619,23 +658,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 +701,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/DeleteGuestsTask.cs b/src/Libraries/SmartStore.Services/Customers/DeleteGuestsTask.cs index 35dbb85d43..1238009e0d 100644 --- a/src/Libraries/SmartStore.Services/Customers/DeleteGuestsTask.cs +++ b/src/Libraries/SmartStore.Services/Customers/DeleteGuestsTask.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using SmartStore.Services.Tasks; namespace SmartStore.Services.Customers @@ -6,23 +7,20 @@ namespace SmartStore.Services.Customers /// /// Represents a task for deleting guest customers /// - public partial class DeleteGuestsTask : ITask + public partial class DeleteGuestsTask : AsyncTask { private readonly ICustomerService _customerService; public DeleteGuestsTask(ICustomerService customerService) { - this._customerService = customerService; + _customerService = customerService; } - /// - /// Executes a task - /// - public void Execute(TaskExecutionContext ctx) - { - //60*24 = 1 day - var olderThanMinutes = 1440; // TODO: move to settings - _customerService.DeleteGuestCustomers(null, DateTime.UtcNow.AddMinutes(-olderThanMinutes), true); - } - } + public override async Task ExecuteAsync(TaskExecutionContext ctx) + { + //60*24 = 1 day + var olderThanMinutes = 1440; // TODO: move to settings + await _customerService.DeleteGuestCustomersAsync(null, DateTime.UtcNow.AddMinutes(-olderThanMinutes), true); + } + } } 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/Events/CustomerLogedInEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs new file mode 100644 index 0000000000..b224692e64 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerLogedInEvent.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.Customers +{ + /// + /// An event message, which will be published after customer has logged in + /// + public class CustomerLogedInEvent + { + public Customer Customer + { + get; + set; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs b/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs similarity index 77% rename from src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs rename to src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs index 76bc89b1de..920c08fca6 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerRegisteredEvent.cs +++ b/src/Libraries/SmartStore.Services/Customers/Events/CustomerRegisteredEvent.cs @@ -4,7 +4,7 @@ namespace SmartStore.Services.Customers { /// - /// An event message, which gets published after customer was registered + /// An event message, which will be published after customer has registered /// public class CustomerRegisteredEvent { diff --git a/src/Libraries/SmartStore.Services/Customers/GdprTool.cs b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs new file mode 100644 index 0000000000..1c2cc5d174 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Customers/GdprTool.cs @@ -0,0 +1,468 @@ +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; +using SmartStore.Core.Domain.Forums; + +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(); + } + + // Forum post votes + var forumPostVotes = customer.CustomerContent.OfType(); + if (forumPostVotes.Any()) + { + ignoreMemberNames = new string[] { "CustomerId", "UpdatedOn" }; + model["ForumPostVotes"] = forumPostVotes.Select(x => _messageModelProvider.CreateModelPart(x, true, ignoreMemberNames)).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..5736bf5fa0 100644 --- a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; @@ -13,66 +14,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 +81,7 @@ IPagedList GetOnlineCustomers(DateTime lastActivityFromUtc, Customer GetCustomerByEmail(string email); /// - /// Get customer by system role + /// Get customer by system name /// /// System name /// Customer @@ -152,29 +124,31 @@ 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 - /// - /// Customer registration from; null to load all customers - /// Customer registration to; null to load all customers - /// A value indicating whether to delete customers only without shopping cart - /// Number of deleted customers - int DeleteGuestCustomers(DateTime? registrationFrom, DateTime? registrationTo, bool onlyWithoutShoppingCart, int maxItemsToDelete = 5000); + /// + /// Delete guest customer records + /// + /// Customer registration from; null to load all customers + /// Customer registration to; null to load all customers + /// A value indicating whether to delete customers only without shopping cart + /// Number of deleted customers + Task DeleteGuestCustomersAsync(DateTime? registrationFrom, DateTime? registrationTo, bool onlyWithoutShoppingCart, int maxItemsToDelete = 5000); #endregion 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..f806f232fe 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using SmartStore.Core.Async; @@ -10,10 +9,10 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Domain.Media; -using SmartStore.Core.Events; using SmartStore.Services.Affiliates; using SmartStore.Services.Common; using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.DataExchange.Import.Events; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Media; @@ -22,7 +21,7 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; @@ -91,9 +90,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 +119,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessCustomers(context, batch, allAffiliateIds); + ProcessCustomers(context, batch, allAffiliateIds, allCustomerNumbers); } catch (Exception exception) { @@ -157,7 +156,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces); } catch (Exception exception) { @@ -195,14 +194,17 @@ protected override void Import(ImportExecuteContext context) { _services.DbContext.AutoDetectChangesEnabled = false; } - } + + context.Services.EventPublisher.Publish(new ImportBatchExecutedEvent(context, batch)); + } } } protected virtual int ProcessCustomers( ImportExecuteContext context, IEnumerable> batch, - List allAffiliateIds) + List allAffiliateIds, + HashSet allCustomerNumbers) { _customerRepository.AutoCommitEnabled = true; @@ -280,6 +282,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 +317,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 +511,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 +563,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(); @@ -579,7 +582,7 @@ protected virtual int ProcessAvatars( var currentPictures = new List(); var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - var image = CreateDownloadImage(urlOrPath, seoName, 1); + var image = CreateDownloadImage(context, urlOrPath, seoName, 1); if (image == null) continue; @@ -604,15 +607,14 @@ protected virtual int ProcessAvatars( currentPictures.Add(picture); } } - - var size = Size.Empty; - pictureBinary = _pictureService.ValidatePicture(pictureBinary, image.MimeType, out size); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); if (pictureBinary != null && pictureBinary.Length > 0) { - var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, size.Width, size.Height, false); - if (picture != null) + var picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + + if (picture != null) { SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs index 645097de1b..0a172ff592 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvDataReader.cs @@ -11,9 +11,6 @@ using System.Collections.Generic; using System.Data; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using LumenWorks.Framework.IO.Csv; namespace SmartStore.Services.DataExchange.Csv @@ -47,20 +44,22 @@ public CsvDataReader(TextReader reader, CsvConfiguration configuration) this.Configuration = configuration; _csv = new CsvReader( - reader, + reader, configuration.HasHeaders, configuration.Delimiter, configuration.Quote, configuration.Escape, configuration.Comment, configuration.TrimValues ? ValueTrimmingOptions.All : ValueTrimmingOptions.None, - configuration.NullValue); - - _csv.SupportsMultiline = configuration.SupportsMultiline; - _csv.SkipEmptyLines = configuration.SkipEmptyLines; - _csv.DefaultHeaderName = configuration.DefaultHeaderName; - _csv.DefaultParseErrorAction = (LumenWorks.Framework.IO.Csv.ParseErrorAction)((int)configuration.DefaultParseErrorAction); - _csv.MissingFieldAction = (LumenWorks.Framework.IO.Csv.MissingFieldAction)((int)configuration.MissingFieldAction); + 4096, + configuration.NullValue) + { + SupportsMultiline = configuration.SupportsMultiline, + SkipEmptyLines = configuration.SkipEmptyLines, + DefaultHeaderName = configuration.DefaultHeaderName, + DefaultParseErrorAction = (LumenWorks.Framework.IO.Csv.ParseErrorAction)((int)configuration.DefaultParseErrorAction), + MissingFieldAction = (LumenWorks.Framework.IO.Csv.MissingFieldAction)((int)configuration.MissingFieldAction) + }; _reader = _csv; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs index 4be79380ee..57c85801ab 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Xml.Serialization; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Services.DataExchange.Export { - [Serializable] + [Serializable] public class DataExportResult { public DataExportResult() @@ -46,19 +47,38 @@ public class ExportFileInfo public int StoreId { get; set; } /// - /// Name of file + /// Name of file. /// public string FileName { get; set; } /// - /// Short optional text that describes the content of the file + /// Short optional text that describes the content of the file. /// public string Label { get; set; } - /// - /// Whether the file contains entity data - /// - public bool IsDataFile { get; set; } - } + /// + /// The related entity type. + /// + public RelatedEntityType? RelatedType { get; set; } + } } + + + public class DataExportPreviewResult + { + public DataExportPreviewResult() + { + Data = new List(); + } + + /// + /// Preview data. + /// + public List Data { get; set; } + + /// + /// Number of total records. + /// + public int TotalRecords { get; set; } + } } 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..6d1b16defc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Web; +using System.Data.Entity; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -41,10 +42,13 @@ using SmartStore.Services.Tax; using SmartStore.Utilities; using SmartStore.Utilities.Threading; +using SmartStore.Collections; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Seo; namespace SmartStore.Services.DataExchange.Export { - public partial class DataExporter : IDataExporter + public partial class DataExporter : IDataExporter { private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); @@ -82,7 +86,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; @@ -95,14 +100,15 @@ public partial class DataExporter : IDataExporter private readonly Lazy _catalogSettings; private readonly Lazy _localizationSettings; private readonly Lazy _taxSettings; + private readonly Lazy _seoSettings; - public DataExporter( + public DataExporter( ICommonServices services, IDbContext dbContext, HttpContextBase httpContext, Lazy priceFormatter, Lazy exportProfileService, - Lazy localizedEntityService, + Lazy localizedEntityService, Lazy languageService, Lazy urlRecordService, Lazy pictureService, @@ -129,7 +135,8 @@ public DataExporter( Lazy deliveryTimeService, Lazy quantityUnitService, Lazy catalogSearchService, - Lazy productUrlHelper, + Lazy downloadService, + Lazy productUrlHelper, Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, @@ -139,7 +146,8 @@ public DataExporter( Lazy customerSettings, Lazy catalogSettings, Lazy localizationSettings, - Lazy taxSettings) + Lazy taxSettings, + Lazy seoSettings) { _services = services; _dbContext = dbContext; @@ -173,6 +181,7 @@ public DataExporter( _deliveryTimeService = deliveryTimeService; _quantityUnitService = quantityUnitService; _catalogSearchService = catalogSearchService; + _downloadService = downloadService; _productUrlHelper = productUrlHelper; _customerRepository = customerRepository; @@ -186,29 +195,54 @@ public DataExporter( _catalogSettings = catalogSettings; _localizationSettings = localizationSettings; _taxSettings = taxSettings; + _seoSettings = seoSettings; T = NullLocalizer.Instance; } public Localizer T { get; set; } - #endregion + #endregion + + #region Utilities + + private LocalizedPropertyCollection CreateTranslationCollection(string keyGroup, IEnumerable entities) + { + if (entities == null || !entities.Any()) + { + return new LocalizedPropertyCollection(keyGroup, null, Enumerable.Empty()); + } + + var collection = _localizedEntityService.Value.GetLocalizedPropertyCollection(keyGroup, entities.Select(x => x.Id).Distinct().ToArray()); + return collection; + } - #region Utilities + private UrlRecordCollection CreateUrlRecordCollection(string entityName, IEnumerable entities) + { + if (entities == null || !entities.Any()) + { + return new UrlRecordCollection(entityName, null, Enumerable.Empty()); + } - private void SetProgress(DataExporterContext ctx, int loadedRecords) + var collection = _urlRecordService.Value.GetUrlRecordCollection(entityName, null, entities.Select(x => x.Id).Distinct().ToArray()); + return collection; + } + + private void SetProgress(DataExporterContext ctx, int loadedRecords) { try { if (!ctx.IsPreview && loadedRecords > 0) { - int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); + var totalRecords = ctx.StatsPerStore.Sum(x => x.Value.TotalRecords); - if (ctx.Request.Profile.Limit > 0 && totalRecords > ctx.Request.Profile.Limit) - totalRecords = ctx.Request.Profile.Limit; + if (ctx.Request.Profile.Limit > 0 && totalRecords > ctx.Request.Profile.Limit) + { + totalRecords = ctx.Request.Profile.Limit; + } ctx.RecordCount = Math.Min(ctx.RecordCount + loadedRecords, totalRecords); - var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount, totalRecords); + var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount.ToString("N0"), totalRecords.ToString("N0")); ctx.Request.ProgressValueSetter.Invoke(ctx.RecordCount, totalRecords, msg); } } @@ -262,11 +296,56 @@ private bool HasPermission(DataExporterContext ctx) return true; } - private void DetachAllEntitiesAndClear(DataExporterContext ctx) + private void StreamToFile(DataExporterContext ctx, Stream stream, string path, Action onDisposed) + { + if (stream != null) + { + try + { + if (ctx.IsFileBasedExport && path.HasValue() && stream.Length > 0) + { + if (!stream.CanSeek) + { + ctx.Log.Warn("Data stream seems to be closed!"); + } + + stream.Seek(0, SeekOrigin.Begin); + + using (_rwLock.GetWriteLock()) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + stream.CopyTo(fileStream); + } + } + } + catch (Exception ex) + { + ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; + ctx.Log.ErrorFormat(ex, $"Failed to stream file {path}."); + ctx.Result.LastError = ex.ToAllMessages(true); + } + finally + { + stream.Dispose(); + onDisposed(stream); + } + } + + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.DeleteFile(path); + } + } + + private void DetachAllEntitiesAndClear(DataExporterContext ctx) { try { - if (ctx.ProductExportContext != null) + ctx.AssociatedProductContext?.Clear(); + ctx.TranslationsPerPage?.Clear(); + ctx.UrlRecordsPerPage?.Clear(); + + if (ctx.ProductExportContext != null) { _dbContext.DetachEntities(x => { @@ -333,33 +412,74 @@ x is Picture || x is ProductBundleItem || x is ProductCategory || x is ProductMa } } - private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) + private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx) { - var offset = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + var offset = Math.Max(ctx.Request.Profile.Offset, 0); var limit = Math.Max(ctx.Request.Profile.Limit, 0); - var recordsPerSegment = (ctx.IsPreview ? 0 : Math.Max(ctx.Request.Profile.BatchSize, 0)); - var totalCount = Math.Max(ctx.Request.Profile.Offset, 0) + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; - - switch (ctx.Request.Provider.Value.EntityType) + var recordsPerSegment = ctx.IsPreview ? 0 : Math.Max(ctx.Request.Profile.BatchSize, 0); + var totalRecords = offset + stats.TotalRecords; + + ctx.LastId = stats.OffsetId; + + switch (ctx.Request.Provider.Value.EntityType) { case ExportEntityType.Product: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetProducts(ctx, skip), + () => GetProducts(ctx), entities => { - // load data behind navigation properties for current queue in one go - ctx.ProductExportContext = CreateProductExportContext(entities, ctx.ContextCustomer, ctx.Store.Id); - }, + // Load data behind navigation properties for current queue in one go. + ctx.ProductExportContext = CreateProductExportContext(entities, ctx.ContextCustomer, ctx.Store.Id); + ctx.AssociatedProductContext = null; + + var context = ctx.ProductExportContext; + if (!ctx.Projection.NoGroupedProducts && entities.Where(x => x.ProductType == ProductType.GroupedProduct).Any()) + { + context.AssociatedProducts.LoadAll(); + var associatedProducts = context.AssociatedProducts.SelectMany(x => x.Value); + ctx.AssociatedProductContext = CreateProductExportContext(associatedProducts, ctx.ContextCustomer, ctx.Store.Id); + + var allProductEntities = entities.Where(x => x.ProductType != ProductType.GroupedProduct).Concat(associatedProducts); + ctx.TranslationsPerPage[nameof(Product)] = CreateTranslationCollection(nameof(Product), allProductEntities); + ctx.UrlRecordsPerPage[nameof(Product)] = CreateUrlRecordCollection(nameof(Product), allProductEntities); + } + else + { + ctx.TranslationsPerPage[nameof(Product)] = CreateTranslationCollection(nameof(Product), entities); + ctx.UrlRecordsPerPage[nameof(Product)] = CreateUrlRecordCollection(nameof(Product), entities); + } + + context.ProductTags.LoadAll(); + context.ProductBundleItems.LoadAll(); + context.SpecificationAttributes.LoadAll(); + context.Attributes.LoadAll(); + + var psa = context.SpecificationAttributes.SelectMany(x => x.Value); + var sao = psa.Select(x => x.SpecificationAttributeOption); + var sa = psa.Select(x => x.SpecificationAttributeOption.SpecificationAttribute); + + var pva = context.Attributes.SelectMany(x => x.Value); + var pvav = pva.SelectMany(x => x.ProductVariantAttributeValues); + var pa = pva.Select(x => x.ProductAttribute); + + ctx.TranslationsPerPage[nameof(ProductTag)] = CreateTranslationCollection(nameof(ProductTag), context.ProductTags.SelectMany(x => x.Value)); + ctx.TranslationsPerPage[nameof(ProductBundleItem)] = CreateTranslationCollection(nameof(ProductBundleItem), context.ProductBundleItems.SelectMany(x => x.Value)); + ctx.TranslationsPerPage[nameof(SpecificationAttribute)] = CreateTranslationCollection(nameof(SpecificationAttribute), sa); + ctx.TranslationsPerPage[nameof(SpecificationAttributeOption)] = CreateTranslationCollection(nameof(SpecificationAttributeOption), sao); + ctx.TranslationsPerPage[nameof(ProductAttribute)] = CreateTranslationCollection(nameof(ProductAttribute), pa); + ctx.TranslationsPerPage[nameof(ProductVariantAttributeValue)] = CreateTranslationCollection(nameof(ProductVariantAttributeValue), pvav); + }, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.Order: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetOrders(ctx, skip), + () => GetOrders(ctx), entities => { ctx.OrderExportContext = new OrderExportContext(entities, @@ -370,16 +490,24 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in x => _orderService.Value.GetOrderItemsByOrderIds(x), x => _shipmentService.Value.GetShipmentsByOrderIds(x) ); - }, + + ctx.OrderExportContext.OrderItems.LoadAll(); + + var orderItems = ctx.OrderExportContext.OrderItems.SelectMany(x => x.Value); + var products = orderItems.Select(x => x.Product); + + ctx.TranslationsPerPage[nameof(Product)] = CreateTranslationCollection(nameof(Product), products); + ctx.UrlRecordsPerPage[nameof(Product)] = CreateUrlRecordCollection(nameof(Product), products); + }, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.Manufacturer: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetManufacturers(ctx, skip), + () => GetManufacturers(ctx), entities => { ctx.ManufacturerExportContext = new ManufacturerExportContext(entities, @@ -388,14 +516,14 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in ); }, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.Category: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetCategories(ctx, skip), + () => GetCategories(ctx), entities => { ctx.CategoryExportContext = new CategoryExportContext(entities, @@ -404,14 +532,14 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in ); }, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.Customer: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetCustomers(ctx, skip), + () => GetCustomers(ctx), entities => { ctx.CustomerExportContext = new CustomerExportContext(entities, @@ -419,27 +547,32 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in ); }, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.NewsLetterSubscription: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetNewsLetterSubscriptions(ctx, skip), + () => GetNewsLetterSubscriptions(ctx), null, entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; case ExportEntityType.ShoppingCartItem: ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( - skip => GetShoppingCartItems(ctx, skip), - null, - entity => Convert(ctx, entity), - offset, PageSize, limit, recordsPerSegment, totalCount + () => GetShoppingCartItems(ctx), + entities => + { + var products = entities.Select(x => x.Product); + ctx.TranslationsPerPage[nameof(Product)] = CreateTranslationCollection(nameof(Product), products); + ctx.UrlRecordsPerPage[nameof(Product)] = CreateUrlRecordCollection(nameof(Product), products); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalRecords ); break; @@ -451,67 +584,106 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in return ctx.ExecuteContext.DataSegmenter as IExportDataSegmenterProvider; } - private bool CallProvider(DataExporterContext ctx, string streamId, string method, string path) + private IEnumerable GetDataUnitsForRelatedEntities(DataExporterContext ctx) + { + // Related data is data without own export provider or importer. For a flat formatted export + // they have to be exported together with metadata to know what to be edited. + RelatedEntityType[] types = null; + + switch (ctx.Request.Provider.Value.EntityType) + { + case ExportEntityType.Product: + types = new RelatedEntityType[] + { + RelatedEntityType.TierPrice, + RelatedEntityType.ProductVariantAttributeValue, + RelatedEntityType.ProductVariantAttributeCombination + }; + break; + default: + return Enumerable.Empty(); + } + + var result = new List(); + var context = ctx.ExecuteContext; + var fileExtension = Path.GetExtension(context.FileName); + + foreach (var type in types) + { + // Convention: Must end with type name because that's how the import identifies the entity. + // Be careful in case of accidents with file names. They must not be too long. + var fileName = $"{ctx.Store.Id}-{context.FileIndex.ToString("D4")}-{type.ToString()}"; + + if (File.Exists(Path.Combine(context.Folder, fileName + fileExtension))) + { + fileName = $"{CommonHelper.GenerateRandomDigitCode(4)}-{fileName}"; + } + + result.Add(new ExportDataUnit + { + RelatedType = type, + DisplayInFileDialog = true, + FileName = fileName + fileExtension, + DataStream = new MemoryStream() + }); + } + + return result; + } + + private bool CallProvider(DataExporterContext ctx, string method, string path) { if (method != "Execute" && method != "OnExecuted") { throw new SmartException($"Unknown export method {method.NaIfEmpty()}."); } + var context = ctx.ExecuteContext; + var provider = ctx.Request.Provider.Value; + try { - ctx.ExecuteContext.DataStreamId = streamId; - - using (ctx.ExecuteContext.DataStream = new MemoryStream()) - { - if (method == "Execute") - { - ctx.Request.Provider.Value.Execute(ctx.ExecuteContext); - } - else if (method == "OnExecuted") - { - ctx.Request.Provider.Value.OnExecuted(ctx.ExecuteContext); - } - - if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) - { - if (!ctx.ExecuteContext.DataStream.CanSeek) - { - ctx.Log.Warn("Data stream seems to be closed!"); - } - - ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); - - using (_rwLock.GetWriteLock()) - using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - { - ctx.Log.Info($"Creating file {path}."); - ctx.ExecuteContext.DataStream.CopyTo(fileStream); - } - } - } - } - catch (Exception exception) + context.DataStream = new MemoryStream(); + + if (method == "Execute") + { + provider.Execute(context); + } + else if (method == "OnExecuted") + { + provider.OnExecuted(context); + } + } + catch (Exception ex) { - ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; - ctx.Log.ErrorFormat(exception, $"The provider failed at the {method.NaIfEmpty()} method."); - ctx.Result.LastError = exception.ToString(); + context.Abort = DataExchangeAbortion.Hard; + ctx.Log.ErrorFormat(ex, $"The provider failed at the {method.NaIfEmpty()} method."); + ctx.Result.LastError = ex.ToAllMessages(true); } finally { - if (ctx.ExecuteContext.DataStream != null) - { - ctx.ExecuteContext.DataStream.Dispose(); - ctx.ExecuteContext.DataStream = null; - } + StreamToFile(ctx, context.DataStream, path, x => context.DataStream = null); - if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) - { - FileSystemHelper.Delete(path); - } + if (method == "Execute") + { + var sb = new StringBuilder(); + sb.Append($"Provider reports {context.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {provider.EntityType.ToString()} to {path.NaIfEmpty()}."); + + foreach (var unit in context.ExtraDataUnits.Where(x => x.RelatedType.HasValue && x.DataStream != null)) + { + // Set unit.DataStream to null so that providers know that this unit should no longer be written to. + // We need these units later for ExportProfile.ResultInfo. + var unitPath = Path.Combine(context.Folder, unit.FileName); + StreamToFile(ctx, unit.DataStream, unitPath, x => unit.DataStream = null); + + sb.Append($"\r\nProvider reports {unit.RecordsSucceeded.ToString("N0")} successfully exported record(s) of type {unit.RelatedType.Value.ToString()} to {unitPath.NaIfEmpty()}."); + } + + ctx.Log.Info(sb.ToString()); + } } - return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); + return context.Abort != DataExchangeAbortion.Hard; } private bool Deploy(DataExporterContext ctx, string zipPath) @@ -569,16 +741,16 @@ private bool Deploy(DataExporterContext ctx, string zipPath) allSucceeded = false; } } - catch (Exception exception) + catch (Exception ex) { allSucceeded = false; if (context.Result != null) { - context.Result.LastError = exception.ToAllMessages(); + context.Result.LastError = ex.ToAllMessages(true); } - ctx.Log.ErrorFormat(exception, "Deployment \"{0}\" of type {1} failed", deployment.Name, deployment.DeploymentType.ToString()); + ctx.Log.Error(ex, $"Deployment \"{deployment.Name}\" of type {deployment.DeploymentType.ToString()} failed."); } deployment.ResultInfo = XmlHelper.Serialize(context.Result); @@ -685,15 +857,17 @@ public virtual ProductExportContext CreateProductExportContext( x => _manufacturerService.Value.GetProductManufacturersByProductIds(x), x => _productService.Value.GetAppliedDiscountsByProductIds(x), x => _productService.Value.GetBundleItemsByProductIds(x, showHidden), + x => _productService.Value.GetAssociatedProductsByProductIds(x), 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; } - private IQueryable GetProductQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetProductQuery(DataExporterContext ctx, int? skip, int take) { IQueryable query = null; @@ -706,8 +880,8 @@ private IQueryable GetProductQuery(DataExporterContext ctx, int skip, i var searchQuery = new CatalogSearchQuery() .WithCurrency(ctx.ContextCurrency) .WithLanguage(ctx.ContextLanguage) - .HasStoreId(ctx.Request.Profile.PerStore ? ctx.Store.Id : f.StoreId) .VisibleIndividuallyOnly(true) + .HasStoreId(ctx.Request.Profile.PerStore ? ctx.Store.Id : f.StoreId) .PriceBetween(f.PriceMinimum, f.PriceMaximum) .WithStockQuantity(f.AvailabilityMinimum, f.AvailabilityMaximum) .CreatedBetween(createdFrom, createdTo); @@ -737,83 +911,108 @@ private IQueryable GetProductQuery(DataExporterContext ctx, int skip, i searchQuery = searchQuery.WithProductId(f.IdMinimum, f.IdMaximum); query = _catalogSearchService.Value.PrepareQuery(searchQuery); - query = query.OrderByDescending(x => x.CreatedOnUtc); } else { query = ctx.Request.ProductQuery; } - if (skip > 0) - query = query.Skip(skip); + query = query.OrderBy(x => x.Id); - if (take != int.MaxValue) - query = query.Take(take); + // Skip required for preview. + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } + + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetProducts(DataExporterContext ctx, int skip) + private List GetProducts(DataExporterContext ctx) { - // we use ctx.EntityIdsPerSegment to avoid exporting products multiple times per segment\file (cause of associated products). - - var result = new List(); - - var products = GetProductQuery(ctx, skip, PageSize).ToList(); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var products = GetProductQuery(ctx, null, PageSize).ToList(); + if (!products.Any()) + { + return null; + } + + var result = new List(); + Multimap associatedProducts = null; + + if (ctx.Projection.NoGroupedProducts) + { + var groupedProductIds = products.Where(x => x.ProductType == ProductType.GroupedProduct).Select(x => x.Id).ToArray(); + associatedProducts = _productService.Value.GetAssociatedProductsByProductIds(groupedProductIds, true); + } foreach (var product in products) { if (product.ProductType == ProductType.SimpleProduct || product.ProductType == ProductType.BundledProduct) { - if (!ctx.EntityIdsPerSegment.Contains(product.Id)) - { - result.Add(product); - ctx.EntityIdsPerSegment.Add(product.Id); - } + // We use ctx.EntityIdsPerSegment to avoid exporting products multiple times per segment\file (cause of associated products). + if (ctx.EntityIdsPerSegment.Add(product.Id)) + { + result.Add(product); + } } else if (product.ProductType == ProductType.GroupedProduct) { if (ctx.Projection.NoGroupedProducts) { - var searchQuery = new CatalogSearchQuery() - .HasParentGroupedProduct(product.Id) - .HasStoreId(ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); - - if (ctx.Projection.OnlyIndividuallyVisibleAssociated) - searchQuery = searchQuery.VisibleIndividuallyOnly(true); - - if (ctx.Filter.IsPublished.HasValue) - searchQuery = searchQuery.PublishedOnly(ctx.Filter.IsPublished.Value); - - var query = _catalogSearchService.Value.PrepareQuery(searchQuery); - var associatedProducts = query.OrderBy(p => p.DisplayOrder).ToList(); - - foreach (var associatedProduct in associatedProducts) - { - if (!ctx.EntityIdsPerSegment.Contains(associatedProduct.Id)) - { - result.Add(associatedProduct); - ctx.EntityIdsPerSegment.Add(associatedProduct.Id); - } - } + if (associatedProducts.ContainsKey(product.Id)) + { + foreach (var associatedProduct in associatedProducts[product.Id]) + { + if (ctx.Projection.OnlyIndividuallyVisibleAssociated && !associatedProduct.VisibleIndividually) + { + continue; + } + if (ctx.Filter.IsPublished.HasValue && ctx.Filter.IsPublished.Value != associatedProduct.Published) + { + continue; + } + + if (ctx.EntityIdsPerSegment.Add(associatedProduct.Id)) + { + result.Add(associatedProduct); + } + } + } } else { - if (!ctx.EntityIdsPerSegment.Contains(product.Id)) + if (ctx.EntityIdsPerSegment.Add(product.Id)) { result.Add(product); - ctx.EntityIdsPerSegment.Add(product.Id); } } } } - SetProgress(ctx, products.Count); + ctx.LastId = products.Last().Id; + SetProgress(ctx, products.Count); return result; } - private IQueryable GetOrderQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetOrderQuery(DataExporterContext ctx, int? skip, int take) { var query = _orderService.Value.GetOrders( ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId, @@ -827,93 +1026,160 @@ private IQueryable GetOrderQuery(DataExporterContext ctx, int skip, int t null, null); - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query.OrderByDescending(x => x.CreatedOnUtc); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetOrders(DataExporterContext ctx, int skip) + private List GetOrders(DataExporterContext ctx) { - var orders = GetOrderQuery(ctx, skip, PageSize).ToList(); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var orders = GetOrderQuery(ctx, null, PageSize).ToList(); + if (!orders.Any()) + { + return null; + } if (ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) { ctx.SetLoadedEntityIds(orders.Select(x => x.Id)); } - SetProgress(ctx, orders.Count); + ctx.LastId = orders.Last().Id; + SetProgress(ctx, orders.Count); return orders; } - private IQueryable GetManufacturerQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetManufacturerQuery(DataExporterContext ctx, int? skip, int take) { var storeId = ctx.Request.Profile.PerStore ? ctx.Store.Id : 0; var query = _manufacturerService.Value.GetManufacturers(true, storeId); - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query.OrderBy(x => x.DisplayOrder); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetManufacturers(DataExporterContext ctx, int skip) + private List GetManufacturers(DataExporterContext ctx) { - var manus = GetManufacturerQuery(ctx, skip, PageSize).ToList(); - - SetProgress(ctx, manus.Count); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var manus = GetManufacturerQuery(ctx, null, PageSize).ToList(); + if (!manus.Any()) + { + return null; + } + + ctx.LastId = manus.Last().Id; + SetProgress(ctx, manus.Count); return manus; } - private IQueryable GetCategoryQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetCategoryQuery(DataExporterContext ctx, int? skip, int take) { var storeId = ctx.Request.Profile.PerStore ? ctx.Store.Id : 0; var query = _categoryService.Value.BuildCategoriesQuery(null, true, null, storeId); - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query - .OrderBy(x => x.ParentCategoryId) - .ThenBy(x => x.DisplayOrder); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetCategories(DataExporterContext ctx, int skip) + private List GetCategories(DataExporterContext ctx) { - var categories = GetCategoryQuery(ctx, skip, PageSize).ToList(); - - SetProgress(ctx, categories.Count); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var categories = GetCategoryQuery(ctx, null, PageSize).ToList(); + if (!categories.Any()) + { + return null; + } + + ctx.LastId = categories.Last().Id; + SetProgress(ctx, categories.Count); return categories; } - private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetCustomerQuery(DataExporterContext ctx, int? skip, int take) { var query = _customerRepository.Value.TableUntracked .Expand(x => x.BillingAddress) @@ -923,20 +1189,30 @@ private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, .Expand(x => x.CustomerRoles) .Where(x => !x.Deleted); - if (ctx.Filter.IsActiveCustomer.HasValue) - query = query.Where(x => x.Active == ctx.Filter.IsActiveCustomer.Value); + if (ctx.Filter.IsActiveCustomer.HasValue) + { + query = query.Where(x => x.Active == ctx.Filter.IsActiveCustomer.Value); + } - if (ctx.Filter.IsTaxExempt.HasValue) - query = query.Where(x => x.IsTaxExempt == ctx.Filter.IsTaxExempt.Value); + if (ctx.Filter.IsTaxExempt.HasValue) + { + query = query.Where(x => x.IsTaxExempt == ctx.Filter.IsTaxExempt.Value); + } - if (ctx.Filter.CustomerRoleIds != null && ctx.Filter.CustomerRoleIds.Length > 0) - query = query.Where(x => x.CustomerRoles.Select(y => y.Id).Intersect(ctx.Filter.CustomerRoleIds).Any()); + if (ctx.Filter.CustomerRoleIds != null && ctx.Filter.CustomerRoleIds.Any()) + { + query = query.Where(x => x.CustomerRoles.Select(y => y.Id).Intersect(ctx.Filter.CustomerRoleIds).Any()); + } - if (ctx.Filter.BillingCountryIds != null && ctx.Filter.BillingCountryIds.Length > 0) - query = query.Where(x => x.BillingAddress != null && ctx.Filter.BillingCountryIds.Contains(x.BillingAddress.Id)); + if (ctx.Filter.BillingCountryIds != null && ctx.Filter.BillingCountryIds.Any()) + { + query = query.Where(x => x.BillingAddress != null && ctx.Filter.BillingCountryIds.Contains(x.BillingAddress.Id)); + } - if (ctx.Filter.ShippingCountryIds != null && ctx.Filter.ShippingCountryIds.Length > 0) - query = query.Where(x => x.ShippingAddress != null && ctx.Filter.ShippingCountryIds.Contains(x.ShippingAddress.Id)); + if (ctx.Filter.ShippingCountryIds != null && ctx.Filter.ShippingCountryIds.Any()) + { + query = query.Where(x => x.ShippingAddress != null && ctx.Filter.ShippingCountryIds.Contains(x.ShippingAddress.Id)); + } if (ctx.Filter.LastActivityFrom.HasValue) { @@ -978,42 +1254,84 @@ private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, .Select(x => x.Customer); } - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query.OrderByDescending(x => x.CreatedOnUtc); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetCustomers(DataExporterContext ctx, int skip) + private List GetCustomers(DataExporterContext ctx) { - var customers = GetCustomerQuery(ctx, skip, PageSize).ToList(); - - SetProgress(ctx, customers.Count); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var customers = GetCustomerQuery(ctx, null, PageSize).ToList(); + if (!customers.Any()) + { + return null; + } + + ctx.LastId = customers.Last().Id; + SetProgress(ctx, customers.Count); return customers; } - private IQueryable GetNewsLetterSubscriptionQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetNewsLetterSubscriptionQuery(DataExporterContext ctx, int? skip, int take) { - var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + var storeId = ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId; var query = _subscriptionRepository.Value.TableUntracked; - if (storeId > 0) - query = query.Where(x => x.StoreId == storeId); - - if (ctx.Filter.IsActiveSubscriber.HasValue) - query = query.Where(x => x.Active == ctx.Filter.IsActiveSubscriber.Value); - - if (ctx.Filter.CreatedFrom.HasValue) + if (storeId > 0) + { + query = query.Where(x => x.StoreId == storeId); + } + + if (ctx.Filter.IsActiveSubscriber.HasValue) + { + query = query.Where(x => x.Active == ctx.Filter.IsActiveSubscriber.Value); + } + + 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); @@ -1025,43 +1343,66 @@ private IQueryable GetNewsLetterSubscriptionQuery(DataEx query = query.Where(x => createdTo >= x.CreatedOnUtc); } - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query - .OrderBy(x => x.StoreId) - .ThenBy(x => x.Email); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(() => take); + } return query; } - private List GetNewsLetterSubscriptions(DataExporterContext ctx, int skip) + private List GetNewsLetterSubscriptions(DataExporterContext ctx) { - var subscriptions = GetNewsLetterSubscriptionQuery(ctx, skip, PageSize).ToList(); - - SetProgress(ctx, subscriptions.Count); + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var subscriptions = GetNewsLetterSubscriptionQuery(ctx, null, PageSize).ToList(); + if (!subscriptions.Any()) + { + return null; + } + + ctx.LastId = subscriptions.Last().Id; + SetProgress(ctx, subscriptions.Count); return subscriptions; } - private IQueryable GetShoppingCartItemQuery(DataExporterContext ctx, int skip, int take) + private IQueryable GetShoppingCartItemQuery(DataExporterContext ctx, int? skip, int take) { - var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + var storeId = ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId; var query = _shoppingCartItemRepository.Value.TableUntracked .Expand(x => x.Customer) .Expand(x => x.Customer.CustomerRoles) .Expand(x => x.Product) - .Where(x => !x.Customer.Deleted); // && !x.Product.Deleted + .Where(x => !x.Customer.Deleted); - if (storeId > 0) - query = query.Where(x => x.StoreId == storeId); + if (storeId > 0) + { + query = query.Where(x => x.StoreId == storeId); + } if (ctx.Request.ActionOrigin.IsCaseInsensitiveEqual("CurrentCarts")) { @@ -1076,14 +1417,20 @@ private IQueryable GetShoppingCartItemQuery(DataExporterContex query = query.Where(x => x.ShoppingCartTypeId == ctx.Filter.ShoppingCartTypeId.Value); } - if (ctx.Filter.IsActiveCustomer.HasValue) - query = query.Where(x => x.Customer.Active == ctx.Filter.IsActiveCustomer.Value); + if (ctx.Filter.IsActiveCustomer.HasValue) + { + query = query.Where(x => x.Customer.Active == ctx.Filter.IsActiveCustomer.Value); + } - if (ctx.Filter.IsTaxExempt.HasValue) - query = query.Where(x => x.Customer.IsTaxExempt == ctx.Filter.IsTaxExempt.Value); + if (ctx.Filter.IsTaxExempt.HasValue) + { + query = query.Where(x => x.Customer.IsTaxExempt == ctx.Filter.IsTaxExempt.Value); + } - if (ctx.Filter.CustomerRoleIds != null && ctx.Filter.CustomerRoleIds.Length > 0) - query = query.Where(x => x.Customer.CustomerRoles.Select(y => y.Id).Intersect(ctx.Filter.CustomerRoleIds).Any()); + if (ctx.Filter.CustomerRoleIds != null && ctx.Filter.CustomerRoleIds.Any()) + { + query = query.Where(x => x.Customer.CustomerRoles.Select(y => y.Id).Intersect(ctx.Filter.CustomerRoleIds).Any()); + } if (ctx.Filter.LastActivityFrom.HasValue) { @@ -1118,173 +1465,225 @@ private IQueryable GetShoppingCartItemQuery(DataExporterContex query = query.Where(x => x.BundleItemId == null); } - if (ctx.Request.EntitiesToExport.Any()) - query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Any()) + { + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + } - query = query - .OrderBy(x => x.ShoppingCartTypeId) - .ThenBy(x => x.CustomerId) - .ThenByDescending(x => x.CreatedOnUtc); + query = query.OrderBy(x => x.Id); - if (skip > 0) - query = query.Skip(skip); + var skipValue = skip.GetValueOrDefault(); + if (skipValue > 0) + { + query = query.Skip(() => skipValue); + } + else if (ctx.LastId > 0) + { + query = query.Where(x => x.Id > ctx.LastId); + } - if (take != int.MaxValue) - query = query.Take(take); + if (take != int.MaxValue) + { + query = query.Take(take); + } return query; } - private List GetShoppingCartItems(DataExporterContext ctx, int skip) + private List GetShoppingCartItems(DataExporterContext ctx) { - var shoppingCartItems = GetShoppingCartItemQuery(ctx, skip, PageSize).ToList(); - - SetProgress(ctx, shoppingCartItems.Count); - - return shoppingCartItems; + var stats = ctx.StatsPerStore[ctx.Store.Id]; + if (ctx.LastId >= stats.MaxId) + { + // End of data reached. + return null; + } + + var cartItems = GetShoppingCartItemQuery(ctx, null, PageSize).ToList(); + if (!cartItems.Any()) + { + return null; + } + + ctx.LastId = cartItems.Last().Id; + SetProgress(ctx, cartItems.Count); + + return cartItems; } #endregion - private List Init(DataExporterContext ctx, int? totalRecords = null) + private List Init(DataExporterContext ctx) { - // 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); - if (!ctx.IsPreview && ctx.Request.Profile.PerStore) + if (ctx.IsPreview) + { + foreach (var name in new string[] { nameof(Currency), nameof(Country), nameof(StateProvince), nameof(DeliveryTime), nameof(QuantityUnit), nameof(Category), nameof(Manufacturer) }) + { + ctx.Translations[name] = new LocalizedPropertyCollection(name, null, Enumerable.Empty()); + } + + foreach (var name in new string[] { nameof(Product), nameof(ProductTag), nameof(ProductBundleItem), nameof(SpecificationAttribute), nameof(SpecificationAttributeOption), + nameof(ProductAttribute), nameof(ProductVariantAttributeValue) }) + { + ctx.TranslationsPerPage[name] = new LocalizedPropertyCollection(name, null, Enumerable.Empty()); + } + + ctx.UrlRecords[nameof(Category)] = new UrlRecordCollection(nameof(Category), null, Enumerable.Empty()); + ctx.UrlRecords[nameof(Manufacturer)] = new UrlRecordCollection(nameof(Manufacturer), null, Enumerable.Empty()); + ctx.UrlRecordsPerPage[nameof(Product)] = new UrlRecordCollection(nameof(Product), null, Enumerable.Empty()); + } + else + { + // Get all translations and slugs for certain entities in one go. + ctx.Translations[nameof(Currency)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(Currency), null); + ctx.Translations[nameof(Country)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(Country), null); + ctx.Translations[nameof(StateProvince)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(StateProvince), null); + ctx.Translations[nameof(DeliveryTime)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(DeliveryTime), null); + ctx.Translations[nameof(QuantityUnit)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(QuantityUnit), null); + ctx.Translations[nameof(Manufacturer)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(Manufacturer), null); + ctx.Translations[nameof(Category)] = _localizedEntityService.Value.GetLocalizedPropertyCollection(nameof(Category), null); + + ctx.UrlRecords[nameof(Category)] = _urlRecordService.Value.GetUrlRecordCollection(nameof(Category), null, null); + ctx.UrlRecords[nameof(Manufacturer)] = _urlRecordService.Value.GetUrlRecordCollection(nameof(Manufacturer), null, null); + } + + if (!ctx.IsPreview && ctx.Request.Profile.PerStore) { result = new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); } else { - int? storeId = (ctx.Filter.StoreId == 0 ? ctx.Projection.StoreId : ctx.Filter.StoreId); - + int? storeId = ctx.Filter.StoreId == 0 ? ctx.Projection.StoreId : ctx.Filter.StoreId; ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); result = new List { ctx.Store }; } - // get total records for progress + // Get record stats for each store. foreach (var store in result) { ctx.Store = store; - int totalCount = 0; - - if (totalRecords.HasValue) - { - totalCount = totalRecords.Value; // speed up preview by not counting total at each page - } - else - { - switch (ctx.Request.Provider.Value.EntityType) - { - case ExportEntityType.Product: - totalCount = GetProductQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.Order: - totalCount = GetOrderQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.Manufacturer: - totalCount = GetManufacturerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.Category: - totalCount = GetCategoryQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.Customer: - totalCount = GetCustomerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.NewsLetterSubscription: - totalCount = GetNewsLetterSubscriptionQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - case ExportEntityType.ShoppingCartItem: - totalCount = GetShoppingCartItemQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); - break; - } - } - - ctx.RecordsPerStore.Add(store.Id, totalCount); + IQueryable query = null; + + switch (ctx.Request.Provider.Value.EntityType) + { + case ExportEntityType.Product: + query = GetProductQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.Order: + query = GetOrderQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.Manufacturer: + query = GetManufacturerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.Category: + query = GetCategoryQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.Customer: + query = GetCustomerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.NewsLetterSubscription: + query = GetNewsLetterSubscriptionQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + case ExportEntityType.ShoppingCartItem: + query = GetShoppingCartItemQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue); + break; + } + + var stats = new RecordStats + { + TotalRecords = query.Count() + }; + + if (!ctx.IsPreview) + { + stats.MaxId = query.Max(x => (int?)x.Id) ?? 0; + if (ctx.Request.Profile.Offset > 0) + { + stats.OffsetId = query.Select(x => x.Id).FirstOrDefault(); + } + } + + ctx.StatsPerStore[store.Id] = stats; } - return result; + return result; } private void ExportCoreInner(DataExporterContext ctx, Store store) { - if (ctx.ExecuteContext.Abort != DataExchangeAbortion.None) - return; - - var fileIndex = 0; - var dataExchangeSettings = _services.Settings.LoadSetting(store.Id); + var context = ctx.ExecuteContext; + var profile = ctx.Request.Profile; + var provider = ctx.Request.Provider; - ctx.Store = store; + if (context.Abort != DataExchangeAbortion.None) + { + return; + } { var logHead = new StringBuilder(); logHead.AppendLine(); logHead.AppendLine(new string('-', 40)); - logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.Append("Export profile:\t\t" + ctx.Request.Profile.Name); - logHead.AppendLine(ctx.Request.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Request.Profile.Id)); - - if (ctx.Request.Provider.Metadata.FriendlyName.HasValue()) - logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Request.Provider.Metadata.FriendlyName, ctx.Request.Profile.ProviderSystemName)); - else - logHead.AppendLine("Export provider:\t{0}".FormatInvariant(ctx.Request.Profile.ProviderSystemName)); - - var plugin = ctx.Request.Provider.Metadata.PluginDescriptor; - logHead.Append("Plugin:\t\t\t"); - logHead.AppendLine(plugin == null ? "".NaIfEmpty() : "{0} ({1}) v.{2}".FormatInvariant(plugin.FriendlyName, plugin.SystemName, plugin.Version.ToString())); - - logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); + logHead.AppendLine("SmartStore.NET: v." + SmartStoreVersion.CurrentFullVersion); + logHead.Append("Export profile: " + profile.Name); + logHead.AppendLine(profile.Id == 0 ? " (volatile)" : $" (Id {profile.Id})"); + + if (provider.Metadata.FriendlyName.HasValue()) + { + logHead.AppendLine($"Export provider: {provider.Metadata.FriendlyName} ({profile.ProviderSystemName})"); + } + else + { + logHead.AppendLine("Export provider: " + profile.ProviderSystemName); + } + + var plugin = provider.Metadata.PluginDescriptor; + logHead.Append("Plugin: "); + logHead.AppendLine(plugin == null ? "".NaIfEmpty() : $"{plugin.FriendlyName} ({plugin.SystemName}) v.{plugin.Version.ToString()}"); + logHead.AppendLine("Entity: " + provider.Value.EntityType.ToString()); try { var uri = new Uri(store.Url); - logHead.AppendLine("Store:\t\t\t{0} (Id {1})".FormatInvariant(uri.DnsSafeHost.NaIfEmpty(), ctx.Store.Id)); + logHead.AppendLine($"Store: {uri.DnsSafeHost.NaIfEmpty()} (Id {store.Id})"); } catch { } var customer = _services.WorkContext.CurrentCustomer; - logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); + logHead.Append("Executed by: " + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); ctx.Log.Info(logHead.ToString()); } - ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); - ctx.ExecuteContext.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; - - var publicDeployment = ctx.Request.Profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); - ctx.ExecuteContext.HasPublicDeployment = (publicDeployment != null); - ctx.ExecuteContext.PublicFolderPath = publicDeployment.GetDeploymentFolder(true); - ctx.ExecuteContext.PublicFolderUrl = publicDeployment.GetPublicFolderUrl(_services, ctx.Store); + var dataExchangeSettings = _services.Settings.LoadSetting(store.Id); + var publicDeployment = profile.Deployments.FirstOrDefault(x => x.DeploymentType == ExportDeploymentType.PublicFolder); - var fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + ctx.Store = store; + context.FileIndex = 0; + context.Store = ToDynamic(ctx, ctx.Store); + context.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; + context.HasPublicDeployment = publicDeployment != null; + context.PublicFolderPath = publicDeployment.GetDeploymentFolder(true); + context.PublicFolderUrl = publicDeployment.GetPublicFolderUrl(_services, ctx.Store); + var fileExtension = provider.Value.FileExtension.HasValue() ? provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""; using (var segmenter = CreateSegmenter(ctx)) { if (segmenter == null) { - throw new SmartException("Unsupported entity type '{0}'.".FormatInvariant(ctx.Request.Provider.Value.EntityType.ToString())); + throw new SmartException($"Unsupported entity type '{provider.Value.EntityType.ToString()}'."); } if (segmenter.TotalRecords <= 0) @@ -1292,87 +1691,103 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.Log.Info("There are no records to export."); } - while (ctx.ExecuteContext.Abort == DataExchangeAbortion.None && segmenter.HasData) + while (context.Abort == DataExchangeAbortion.None && segmenter.HasData) { segmenter.RecordPerSegmentCount = 0; - ctx.ExecuteContext.RecordsSucceeded = 0; + context.RecordsSucceeded = 0; string path = null; if (ctx.IsFileBasedExport) { - var resolvedPattern = ctx.Request.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); - - ctx.ExecuteContext.FileName = resolvedPattern + fileExtension; - path = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); + context.FileIndex = context.FileIndex + 1; + context.FileName = profile.ResolveFileNamePattern(ctx.Store, context.FileIndex, context.MaxFileNameLength) + fileExtension; + path = Path.Combine(context.Folder, context.FileName); + + if (profile.ExportRelatedData && ctx.Supports(ExportFeatures.UsesRelatedDataUnits)) + { + context.ExtraDataUnits.AddRange(GetDataUnitsForRelatedEntities(ctx)); + } } - if (CallProvider(ctx, null, "Execute", path)) + if (CallProvider(ctx, "Execute", path)) { - ctx.Log.Info("Provider reports {0} successfully exported record(s).".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); - if (ctx.IsFileBasedExport && File.Exists(path)) { ctx.Result.Files.Add(new DataExportResult.ExportFileInfo { StoreId = ctx.Store.Id, - FileName = ctx.ExecuteContext.FileName, - IsDataFile = true - }); - } + FileName = context.FileName + }); + } } - ctx.EntityIdsPerSegment.Clear(); - - if (ctx.ExecuteContext.IsMaxFailures) - ctx.Log.Warn("Export aborted. The maximum number of failures has been reached."); - - if (ctx.CancellationToken.IsCancellationRequested) - ctx.Log.Warn("Export aborted. A cancellation has been requested."); - - DetachAllEntitiesAndClear(ctx); + ctx.EntityIdsPerSegment.Clear(); + DetachAllEntitiesAndClear(ctx); + + if (context.IsMaxFailures) + { + ctx.Log.Warn("Export aborted. The maximum number of failures has been reached."); + } + if (ctx.CancellationToken.IsCancellationRequested) + { + ctx.Log.Warn("Export aborted. A cancellation has been requested."); + } } - if (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) + if (context.Abort != DataExchangeAbortion.Hard) { - // always call OnExecuted - if (ctx.ExecuteContext.ExtraDataUnits.Count == 0) - ctx.ExecuteContext.ExtraDataUnits.Add(new ExportDataUnit()); + var calledExecuted = false; - ctx.ExecuteContext.ExtraDataUnits.ForEach(x => + context.ExtraDataUnits.ForEach(x => { - var path = (x.FileName.HasValue() ? Path.Combine(ctx.ExecuteContext.Folder, x.FileName) : null); - if (CallProvider(ctx, x.Id, "OnExecuted", path)) - { - if (x.DisplayInFileDialog && ctx.IsFileBasedExport && File.Exists(path)) - { - // save info about extra file - ctx.Result.Files.Add(new DataExportResult.ExportFileInfo - { - StoreId = ctx.Store.Id, - FileName = x.FileName, - Label = x.Label, - IsDataFile = false - }); - } - } - }); - - ctx.ExecuteContext.ExtraDataUnits.Clear(); - } - } + context.DataStreamId = x.Id; + + var success = true; + var path = x.FileName.HasValue() ? Path.Combine(context.Folder, x.FileName) : null; + if (!x.RelatedType.HasValue) + { + calledExecuted = true; + success = CallProvider(ctx, "OnExecuted", path); + } + + if (success && ctx.IsFileBasedExport && x.DisplayInFileDialog && File.Exists(path)) + { + // Save info about extra file. + ctx.Result.Files.Add(new DataExportResult.ExportFileInfo + { + StoreId = ctx.Store.Id, + FileName = x.FileName, + Label = x.Label, + RelatedType = x.RelatedType + }); + } + }); + + if (!calledExecuted) + { + // Always call OnExecuted. + CallProvider(ctx, "OnExecuted", null); + } + } + + context.ExtraDataUnits.Clear(); + } } private void ExportCoreOuter(DataExporterContext ctx) { - if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) - return; + var profile = ctx.Request.Profile; + if (profile == null || !profile.Enabled) + { + return; + } - var logPath = ctx.Request.Profile.GetExportLogPath(); - var zipPath = ctx.Request.Profile.GetExportZipPath(); + var logPath = profile.GetExportLogPath(); + var zipPath = profile.GetExportZipPath(); - FileSystemHelper.Delete(logPath); - FileSystemHelper.Delete(zipPath); + FileSystemHelper.DeleteFile(logPath); + FileSystemHelper.DeleteFile(zipPath); FileSystemHelper.ClearDirectory(ctx.FolderContent, false); using (var logger = new TraceLogger(logPath)) @@ -1394,25 +1809,24 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.CustomProperties.Add(item.Key, item.Value); } - if (ctx.Request.Profile.ProviderConfigData.HasValue()) + if (profile.ProviderConfigData.HasValue()) { var configInfo = ctx.Request.Provider.Value.ConfigurationInfo; if (configInfo != null) { - ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Request.Profile.ProviderConfigData, configInfo.ModelType); + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); } } - // lazyLoading: false, proxyCreation: false impossible. how to identify all properties of all data levels of all entities - // that require manual resolving for now and for future? fragile, susceptible to faults (e.g. price calculation)... - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) + // lazyLoading: false, proxyCreation: false impossible due to price calculation. + using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { ctx.DeliveryTimes = _deliveryTimeService.Value.GetAllDeliveryTimes().ToDictionary(x => x.Id); ctx.QuantityUnits = _quantityUnitService.Value.GetAllQuantityUnits().ToDictionary(x => x.Id); ctx.ProductTemplates = _productTemplateService.Value.GetAllProductTemplates().ToDictionary(x => x.Id, x => x.ViewPath); ctx.CategoryTemplates = _categoryTemplateService.Value.GetAllCategoryTemplates().ToDictionary(x => x.Id, x => x.ViewPath); - if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product || + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product || ctx.Request.Provider.Value.EntityType == ExportEntityType.Order) { ctx.Countries = _countryService.Value.GetAllCountries(true).ToDictionary(x => x.Id, x => x); @@ -1434,7 +1848,7 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Language = ToDynamic(ctx, ctx.ContextLanguage); ctx.ExecuteContext.Customer = ToDynamic(ctx, ctx.ContextCustomer); ctx.ExecuteContext.Currency = ToDynamic(ctx, ctx.ContextCurrency); - ctx.ExecuteContext.Profile = ToDynamic(ctx, ctx.Request.Profile); + ctx.ExecuteContext.Profile = ToDynamic(ctx, profile); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -1443,18 +1857,18 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport) { - if (ctx.Request.Profile.CreateZipArchive) + if (profile.CreateZipArchive) { ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, false); } - if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) + if (profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Publishing")); var allDeploymentsSucceeded = Deploy(ctx, zipPath); - if (allDeploymentsSucceeded && ctx.Request.Profile.Cleanup) + if (allDeploymentsSucceeded && profile.Cleanup) { logger.Info("Cleaning up export folder"); @@ -1463,36 +1877,37 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - if (ctx.Request.Profile.EmailAccountId != 0 && !ctx.Supports(ExportFeatures.CanOmitCompletionMail)) + if (profile.EmailAccountId != 0 && !ctx.Supports(ExportFeatures.CanOmitCompletionMail)) { SendCompletionEmail(ctx, zipPath); } } } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); - ctx.Result.LastError = exception.ToString(); + logger.ErrorsAll(ex); + ctx.Result.LastError = ex.ToAllMessages(true); } finally { try { - if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) + if (!ctx.IsPreview && profile.Id != 0) { - ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + ctx.Result.Files = ctx.Result.Files.OrderBy(x => x.RelatedType).ToList(); + profile.ResultInfo = XmlHelper.Serialize(ctx.Result); - _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); + _exportProfileService.Value.UpdateExportProfile(profile); } } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } DetachAllEntitiesAndClear(ctx); - try + try { ctx.NewsletterSubscriptions.Clear(); ctx.ProductTemplates.Clear(); @@ -1502,6 +1917,8 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.QuantityUnits.Clear(); ctx.DeliveryTimes.Clear(); ctx.Stores.Clear(); + ctx.Translations.Clear(); + ctx.UrlRecords.Clear(); ctx.Request.CustomData.Clear(); @@ -1509,15 +1926,17 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Log = null; ctx.Log = null; } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } } } - if (ctx.IsPreview || ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) - return; + if (ctx.IsPreview || ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) + { + return; + } // Post process order entities. if (ctx.EntityIdsLoaded.Any() && ctx.Request.Provider.Value.EntityType == ExportEntityType.Order && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) @@ -1528,10 +1947,14 @@ private void ExportCoreOuter(DataExporterContext ctx) { int? orderStatusId = null; - if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Processing) - orderStatusId = (int)OrderStatus.Processing; - else if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Complete) - orderStatusId = (int)OrderStatus.Complete; + if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Processing) + { + orderStatusId = (int)OrderStatus.Processing; + } + else if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Complete) + { + orderStatusId = (int)OrderStatus.Complete; + } using (var scope = new DbContextScope(_dbContext, false, null, false, false, false, false)) { @@ -1545,12 +1968,12 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - logger.Info("Updated order status for {0} order(s).".FormatInvariant(ctx.EntityIdsLoaded.Count())); + logger.Info($"Updated order status for {ctx.EntityIdsLoaded.Count()} order(s)."); } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); - ctx.Result.LastError = exception.ToString(); + logger.ErrorsAll(ex); + ctx.Result.LastError = ex.ToAllMessages(true); } } } @@ -1568,83 +1991,74 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc var ctx = new DataExporterContext(request, cancellationToken); ExportCoreOuter(ctx); - cancellationToken.ThrowIfCancellationRequested(); return ctx.Result; } - public IList Preview(DataExportRequest request, int pageIndex, int? totalRecords = null) + public DataExportPreviewResult Preview(DataExportRequest request, int pageIndex) { - var result = new List(); - var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); + var result = new DataExportPreviewResult(); + var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); var ctx = new DataExporterContext(request, cancellation.Token, true); - var unused = Init(ctx, totalRecords); - var offset = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); + var unused = Init(ctx); + var skip = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); if (!HasPermission(ctx)) { throw new SmartException(T("Admin.AccessDenied")); } - switch (request.Provider.Value.EntityType) + result.TotalRecords = ctx.StatsPerStore.First().Value.TotalRecords; + + switch (request.Provider.Value.EntityType) { case ExportEntityType.Product: { - var items = GetProductQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetProductQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.Order: { - var items = GetOrderQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetOrderQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.Category: { - var items = GetCategoryQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetCategoryQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.Manufacturer: { - var items = GetManufacturerQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetManufacturerQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.Customer: { - var items = GetCustomerQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetCustomerQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.NewsLetterSubscription: { - var items = GetNewsLetterSubscriptionQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetNewsLetterSubscriptionQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; case ExportEntityType.ShoppingCartItem: { - var items = GetShoppingCartItemQuery(ctx, offset, PageSize).ToList(); - items.Each(x => result.Add(ToDynamic(ctx, x))); + var items = GetShoppingCartItemQuery(ctx, skip, PageSize).ToList(); + items.Each(x => result.Data.Add(ToDynamic(ctx, x))); } break; } - return result; - } - - public int GetDataCount(DataExportRequest request) - { - var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new DataExporterContext(request, cancellation.Token, true); - var unused = Init(ctx); - - var totalCount = ctx.RecordsPerStore.First().Value; - return totalCount; + return result; } } } 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/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 8fdfe9ba43..346cf02176 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -19,7 +19,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); } - context.Log.Info("Copied export data files to " + targetFolder); + context.Log.Info($"Copied export data files to {targetFolder}."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index a008e69b0c..7e9d67beb3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -27,7 +27,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de File.Copy(context.ZipPath, destinationFile, true); - context.Log.Info("Copied zipped export data to " + destinationFile); + context.Log.Info($"Copied zipped export data to {destinationFile}."); } } else @@ -37,7 +37,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); } - context.Log.Info("Copied export data files to " + destinationFolder); + context.Log.Info($"Copied export data files to {destinationFolder}."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 24ce121d28..8c89087b72 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -22,27 +22,28 @@ using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Html; using SmartStore.Services.Catalog; using SmartStore.Services.Catalog.Modelling; +using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.Export.Events; using SmartStore.Services.DataExchange.Export.Internal; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; +using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Export { - public partial class DataExporter + public partial class DataExporter { private readonly string[] _orderCustomerAttributes = new string[] { SystemCustomerAttributeNames.Gender, - SystemCustomerAttributeNames.DateOfBirth, SystemCustomerAttributeNames.VatNumber, SystemCustomerAttributeNames.VatNumberStatusId, SystemCustomerAttributeNames.TimeZoneId, - SystemCustomerAttributeNames.CustomerNumber, SystemCustomerAttributeNames.ImpersonatedCustomerId }; @@ -50,13 +51,13 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec { try { - var languageId = (ctx.Projection.LanguageId ?? 0); + var languageId = ctx.LanguageId; string description = ""; - // description merging + // Description merging. if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.None) { - // export empty description + // Export empty description. } else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty) { @@ -88,37 +89,38 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec { var productManus = ctx.ProductExportContext.ProductManufacturers.GetOrLoad(product.Id); - if (productManus != null && productManus.Any()) - description = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); + if (productManus != null && productManus.Any()) + { + var translations = ctx.Translations[nameof(Manufacturer)]; + var manufacturer = productManus.First().Manufacturer; + description = translations.GetValue(languageId, manufacturer.Id, nameof(manufacturer.Name)) ?? manufacturer.Name; + } description = description.Grow((string)dynObject.Name, " "); - - if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) - description = description.Grow((string)dynObject.ShortDescription, " "); - else - description = description.Grow((string)dynObject.FullDescription, " "); + description = ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription + ? description.Grow((string)dynObject.ShortDescription, " ") + : description.Grow((string)dynObject.FullDescription, " "); } - // append text + // Append text. if (ctx.Projection.AppendDescriptionText.HasValue() && ((string)dynObject.ShortDescription).IsEmpty() && ((string)dynObject.FullDescription).IsEmpty()) { string[] appendText = ctx.Projection.AppendDescriptionText.SplitSafe(","); if (appendText.Length > 0) { - var rnd = (new Random()).Next(0, appendText.Length - 1); - + var rnd = CommonHelper.GenerateRandomInteger(0, appendText.Length - 1); description = description.Grow(appendText.SafeGet(rnd), " "); } } - // remove critical characters + // Remove critical characters. if (description.HasValue() && ctx.Projection.RemoveCriticalCharacters) { foreach (var str in ctx.Projection.CriticalCharacters.SplitSafe(",")) description = description.Replace(str, ""); } - // convert to plain text + // Convert to plain text. if (description.HasValue() && ctx.Projection.DescriptionToPlainText) { //Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); @@ -160,95 +162,129 @@ private decimal CalculatePrice( ICollection attributeValues) { var price = product.Price; - var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; + var productContext = ctx.ProductExportContext as PriceCalculationContext; + var associatedProductContext = ctx.AssociatedProductContext as PriceCalculationContext; - if (combination != null) + if (combination != null) { // price for attribute combination var attributesTotalPriceBase = decimal.Zero; if (attributeValues != null) { - attributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.Value.GetProductVariantAttributeValuePriceAdjustment(x, product, ctx.ContextCustomer, priceCalculationContext)); + attributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.Value.GetProductVariantAttributeValuePriceAdjustment(x, product, ctx.ContextCustomer, productContext)); } - price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, attributesTotalPriceBase, true, 1, null, priceCalculationContext); + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, attributesTotalPriceBase, true, 1, null, productContext); } else if (ctx.Projection.PriceType.HasValue) - { - // price for product - if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) - { - bool displayFromMessage; - price = _priceCalculationService.Value.GetLowestPrice(product, ctx.ContextCustomer, priceCalculationContext, out displayFromMessage); - } - else if (ctx.Projection.PriceType.Value == PriceDisplayType.PreSelectedPrice) - { - price = _priceCalculationService.Value.GetPreselectedPrice(product, ctx.ContextCustomer, ctx.ContextCurrency, priceCalculationContext); - } - else if (ctx.Projection.PriceType.Value == PriceDisplayType.PriceWithoutDiscountsAndAttributes) - { - price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, priceCalculationContext); - } + { + var priceType = ctx.Projection.PriceType.Value; + + if (product.ProductType == ProductType.GroupedProduct) + { + var associatedProducts = productContext.AssociatedProducts.GetOrLoad(product.Id); + if (associatedProducts.Any()) + { + var firstAssociatedProduct = associatedProducts.First(); + + if (priceType == PriceDisplayType.PreSelectedPrice) + { + price = _priceCalculationService.Value.GetPreselectedPrice(firstAssociatedProduct, ctx.ContextCustomer, ctx.ContextCurrency, associatedProductContext); + } + else if (priceType == PriceDisplayType.PriceWithoutDiscountsAndAttributes) + { + price = _priceCalculationService.Value.GetFinalPrice(firstAssociatedProduct, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, associatedProductContext); + } + else if (priceType == PriceDisplayType.LowestPrice) + { + price = _priceCalculationService.Value.GetLowestPrice(product, ctx.ContextCustomer, associatedProductContext, associatedProducts, out _) ?? decimal.Zero; + } + } + } + else + { + if (priceType == PriceDisplayType.PreSelectedPrice) + { + price = _priceCalculationService.Value.GetPreselectedPrice(product, ctx.ContextCustomer, ctx.ContextCurrency, productContext); + } + else if (priceType == PriceDisplayType.PriceWithoutDiscountsAndAttributes) + { + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, productContext); + } + else if (priceType == PriceDisplayType.LowestPrice) + { + price = _priceCalculationService.Value.GetLowestPrice(product, ctx.ContextCustomer, productContext, out _); + } + } } return ConvertPrice(ctx, product, price) ?? price; } - private List GetLocalized(DataExporterContext ctx, T entity, params Expression>[] keySelectors) - where T : BaseEntity, ILocalizedEntity - { - if (ctx.Languages.Count <= 1) - return null; - - var localized = new List(); - - var localeKeyGroup = typeof(T).Name; - var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); - - foreach (var language in ctx.Languages) - { - var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); - - // add SeName - if (isSlugSupported) - { - var value = _urlRecordService.Value.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); - if (value.HasValue()) - { - dynamic exp = new HybridExpando(); - exp.Culture = languageCulture; - exp.LocaleKey = "SeName"; - exp.LocaleValue = value; + private List GetLocalized( + DataExporterContext ctx, + LocalizedPropertyCollection translations, + UrlRecordCollection urlRecords, + T entity, + params Expression>[] keySelectors) + where T : BaseEntity, ILocalizedEntity + { + Guard.NotNull(translations, nameof(translations)); + + if (ctx.Languages.Count <= 1) + { + return null; + } - localized.Add(exp); - } - } + var localized = new List(); + var localeKeyGroup = entity.GetEntityName(); + //var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); - foreach (var keySelector in keySelectors) - { - var member = keySelector.Body as MemberExpression; - var propInfo = member.Member as PropertyInfo; - string localeKey = propInfo.Name; - var value = _localizedEntityService.Value.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); + foreach (var language in ctx.Languages) + { + var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); - // we better not export empty values. the risk is to high that they are imported and unnecessary fill databases. - if (value.HasValue()) - { - dynamic exp = new HybridExpando(); - exp.Culture = languageCulture; - exp.LocaleKey = localeKey; - exp.LocaleValue = value; + // Add SEO name. + if (urlRecords != null) + { + var value = urlRecords.GetSlug(language.Value.Id, entity.Id, false); + if (value.HasValue()) + { + dynamic exp = new HybridExpando(); + exp.Culture = languageCulture; + exp.LocaleKey = "SeName"; + exp.LocaleValue = value; + + localized.Add(exp); + } + } - localized.Add(exp); - } - } - } + // Add localized property value. + foreach (var keySelector in keySelectors) + { + var member = keySelector.Body as MemberExpression; + var propInfo = member.Member as PropertyInfo; + string localeKey = propInfo.Name; + var value = translations.GetValue(language.Value.Id, entity.Id, localeKey); + + // We do not export empty values to not fill databases with it. + if (value.HasValue()) + { + dynamic exp = new HybridExpando(); + exp.Culture = languageCulture; + exp.LocaleKey = localeKey; + exp.LocaleValue = value; + + localized.Add(exp); + } + } + } - return (localized.Count == 0 ? null : localized); - } + return localized.Any() ? localized : null; + } - private dynamic ToDynamic(DataExporterContext ctx, ExportProfile profile) + private dynamic ToDynamic(DataExporterContext ctx, ExportProfile profile) { if (profile == null) return null; @@ -259,21 +295,26 @@ private dynamic ToDynamic(DataExporterContext ctx, ExportProfile profile) private dynamic ToDynamic(DataExporterContext ctx, Currency currency) { - if (currency == null) - return null; + if (currency == null) + { + return null; + } dynamic result = new DynamicEntity(currency); + var translations = ctx.Translations[nameof(Currency)]; - result.Name = currency.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result._Localized = GetLocalized(ctx, currency, x => x.Name); + result.Name = translations.GetValue(ctx.LanguageId, currency.Id, nameof(currency.Name)) ?? currency.Name; + result._Localized = GetLocalized(ctx, translations, null, currency, x => x.Name); return result; } private dynamic ToDynamic(DataExporterContext ctx, Language language) { - if (language == null) - return null; + if (language == null) + { + return null; + } dynamic result = new DynamicEntity(language); return result; @@ -281,32 +322,38 @@ private dynamic ToDynamic(DataExporterContext ctx, Language language) private dynamic ToDynamic(DataExporterContext ctx, Country country) { - if (country == null) - return null; + if (country == null) + { + return null; + } dynamic result = new DynamicEntity(country); + var translations = ctx.Translations[nameof(Country)]; - result.Name = country.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result._Localized = GetLocalized(ctx, country, x => x.Name); + result.Name = translations.GetValue(ctx.LanguageId, country.Id, nameof(country.Name)) ?? country.Name; + result._Localized = GetLocalized(ctx, translations, null, country, x => x.Name); return result; } private dynamic ToDynamic(DataExporterContext ctx, Address address) { - if (address == null) - return null; + if (address == null) + { + return null; + } dynamic result = new DynamicEntity(address); - result.Country = ToDynamic(ctx, address.Country); + result.Country = ToDynamic(ctx, address.Country); if (address.StateProvinceId.GetValueOrDefault() > 0) { dynamic sp = new DynamicEntity(address.StateProvince); + var translations = ctx.Translations[nameof(StateProvince)]; - sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); + sp.Name = translations.GetValue(ctx.LanguageId, address.StateProvince.Id, nameof(StateProvince)) ?? address.StateProvince.Name; + sp._Localized = GetLocalized(ctx, translations, null, address.StateProvince, x => x.Name); result.StateProvince = sp; } @@ -320,18 +367,21 @@ private dynamic ToDynamic(DataExporterContext ctx, Address address) private dynamic ToDynamic(DataExporterContext ctx, RewardPointsHistory points) { - if (points == null) - return null; + if (points == null) + { + return null; + } dynamic result = new DynamicEntity(points); - return result; } private dynamic ToDynamic(DataExporterContext ctx, Customer customer) { - if (customer == null) - return null; + if (customer == null) + { + return null; + } dynamic result = new DynamicEntity(customer); @@ -361,8 +411,10 @@ private dynamic ToDynamic(DataExporterContext ctx, Customer customer) private dynamic ToDynamic(DataExporterContext ctx, Store store) { - if (store == null) - return null; + if (store == null) + { + return null; + } dynamic result = new DynamicEntity(store); @@ -374,13 +426,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Store store) private dynamic ToDynamic(DataExporterContext ctx, DeliveryTime deliveryTime) { - if (deliveryTime == null) - return null; + if (deliveryTime == null) + { + return null; + } dynamic result = new DynamicEntity(deliveryTime); + var translations = ctx.Translations[nameof(DeliveryTime)]; - result.Name = deliveryTime.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result._Localized = GetLocalized(ctx, deliveryTime, x => x.Name); + result.Name = translations.GetValue(ctx.LanguageId, deliveryTime.Id, nameof(deliveryTime.Name)) ?? deliveryTime.Name; + result._Localized = GetLocalized(ctx, translations, null, deliveryTime, x => x.Name); return result; } @@ -389,25 +444,29 @@ private void ToDeliveryTime(DataExporterContext ctx, dynamic parent, int? delive { if (ctx.DeliveryTimes != null) { - if (deliveryTimeId.HasValue && ctx.DeliveryTimes.ContainsKey(deliveryTimeId.Value)) - parent.DeliveryTime = ToDynamic(ctx, ctx.DeliveryTimes[deliveryTimeId.Value]); - else - parent.DeliveryTime = null; + parent.DeliveryTime = deliveryTimeId.HasValue && ctx.DeliveryTimes.ContainsKey(deliveryTimeId.Value) + ? ToDynamic(ctx, ctx.DeliveryTimes[deliveryTimeId.Value]) + : null; } } private dynamic ToDynamic(DataExporterContext ctx, QuantityUnit quantityUnit) { - if (quantityUnit == null) - return null; + if (quantityUnit == null) + { + return null; + } dynamic result = new DynamicEntity(quantityUnit); + var translations = ctx.Translations[nameof(QuantityUnit)]; - result.Name = quantityUnit.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result.Description = quantityUnit.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); + result.Name = translations.GetValue(ctx.LanguageId, quantityUnit.Id, nameof(quantityUnit.Name)) ?? quantityUnit.Name; + result.NamePlural = translations.GetValue(ctx.LanguageId, quantityUnit.Id, nameof(quantityUnit.NamePlural)) ?? quantityUnit.NamePlural; + result.Description = translations.GetValue(ctx.LanguageId, quantityUnit.Id, nameof(quantityUnit.Description)) ?? quantityUnit.Description; - result._Localized = GetLocalized(ctx, quantityUnit, + result._Localized = GetLocalized(ctx, translations, null, quantityUnit, x => x.Name, + x => x.NamePlural, x => x.Description); return result; @@ -417,20 +476,20 @@ private void ToQuantityUnit(DataExporterContext ctx, dynamic parent, int? quanti { if (ctx.QuantityUnits != null) { - if (quantityUnitId.HasValue && ctx.QuantityUnits.ContainsKey(quantityUnitId.Value)) - parent.QuantityUnit = ToDynamic(ctx, ctx.QuantityUnits[quantityUnitId.Value]); - else - parent.QuantityUnit = null; + parent.QuantityUnit = quantityUnitId.HasValue && ctx.QuantityUnits.ContainsKey(quantityUnitId.Value) + ? ToDynamic(ctx, ctx.QuantityUnits[quantityUnitId.Value]) + : null; } } private dynamic ToDynamic(DataExporterContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) { - if (picture == null) - return null; + if (picture == null) + { + return null; + } // TODO: (mc) Refactor > GetPictureInfo - dynamic result = new DynamicEntity(picture); var pictureInfo = _pictureService.Value.GetPictureInfo(picture); var host = _services.StoreService.GetHost(ctx.Store); @@ -442,8 +501,6 @@ private dynamic ToDynamic(DataExporterContext ctx, Picture picture, int thumbPic result._ThumbImageUrl = _pictureService.Value.GetUrl(pictureInfo, thumbPictureSize, FallbackPictureType.NoFallback, host); result._ImageUrl = _pictureService.Value.GetUrl(pictureInfo, detailsPictureSize, FallbackPictureType.NoFallback, host); result._FullSizeImageUrl = _pictureService.Value.GetUrl(pictureInfo, 0, FallbackPictureType.NoFallback, host); - - //result._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); } return result; @@ -451,34 +508,40 @@ private dynamic ToDynamic(DataExporterContext ctx, Picture picture, int thumbPic private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttribute pva) { - if (pva == null) - return null; + if (pva == null) + { + return null; + } - dynamic result = new DynamicEntity(pva); + var languageId = ctx.LanguageId; + var attribute = pva.ProductAttribute; - dynamic attribute = new DynamicEntity(pva.ProductAttribute); + dynamic result = new DynamicEntity(pva); + dynamic dynAttribute = new DynamicEntity(attribute); + var paTranslations = ctx.TranslationsPerPage[nameof(ProductAttribute)]; + var pvavTranslations = ctx.TranslationsPerPage[nameof(ProductVariantAttributeValue)]; - attribute.Name = pva.ProductAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - attribute.Description = pva.ProductAttribute.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); + dynAttribute.Name = paTranslations.GetValue(languageId, attribute.Id, nameof(attribute.Name)) ?? attribute.Name; + dynAttribute.Description = paTranslations.GetValue(languageId, attribute.Id, nameof(attribute.Description)) ?? attribute.Description; - attribute.Values = pva.ProductVariantAttributeValues + dynAttribute.Values = pva.ProductVariantAttributeValues .OrderBy(x => x.DisplayOrder) .Select(x => { dynamic dyn = new DynamicEntity(x); - dyn.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); - dyn._Localized = GetLocalized(ctx, x, y => y.Name); + dyn.Name = pvavTranslations.GetValue(languageId, x.Id, nameof(x.Name)) ?? x.Name; + dyn._Localized = GetLocalized(ctx, pvavTranslations, null, x, y => y.Name); return dyn; }) .ToList(); - attribute._Localized = GetLocalized(ctx, pva.ProductAttribute, + dynAttribute._Localized = GetLocalized(ctx, paTranslations, null, attribute, x => x.Name, x => x.Description); - result.Attribute = attribute; + result.Attribute = dynAttribute; return result; } @@ -498,23 +561,27 @@ private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttributeCombin private dynamic ToDynamic(DataExporterContext ctx, Manufacturer manufacturer) { - if (manufacturer == null) - return null; + if (manufacturer == null) + { + return null; + } dynamic result = new DynamicEntity(manufacturer); + var translations = ctx.Translations[nameof(Manufacturer)]; + var urlRecords = ctx.UrlRecords[nameof(Manufacturer)]; - result.Picture = null; - result.Name = manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + result.Picture = null; + result.Name = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.Name)) ?? manufacturer.Name; - if (!ctx.IsPreview) + if (!ctx.IsPreview) { - result.SeName = manufacturer.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); - result.Description = manufacturer.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaKeywords = manufacturer.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaDescription = manufacturer.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaTitle = manufacturer.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + result.SeName = ctx.UrlRecords[nameof(Manufacturer)].GetSlug(ctx.LanguageId, manufacturer.Id); + result.Description = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.Description)) ?? manufacturer.Description; + result.MetaKeywords = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.MetaKeywords)) ?? manufacturer.MetaKeywords; + result.MetaDescription = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.MetaDescription)) ?? manufacturer.MetaDescription; + result.MetaTitle = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.MetaTitle)) ?? manufacturer.MetaTitle; - result._Localized = GetLocalized(ctx, manufacturer, + result._Localized = GetLocalized(ctx, translations, urlRecords, manufacturer, x => x.Name, x => x.Description, x => x.MetaKeywords, @@ -527,29 +594,33 @@ private dynamic ToDynamic(DataExporterContext ctx, Manufacturer manufacturer) private dynamic ToDynamic(DataExporterContext ctx, Category category) { - if (category == null) - return null; + if (category == null) + { + return null; + } dynamic result = new DynamicEntity(category); + var translations = ctx.Translations[nameof(Category)]; + var urlRecords = ctx.UrlRecords[nameof(Category)]; - result.Picture = null; - result.Name = category.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - result.FullName = category.GetLocalized(x => x.FullName, ctx.Projection.LanguageId ?? 0, true, false); + result.Picture = null; + result.Name = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.Name)) ?? category.Name; + result.FullName = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.FullName)) ?? category.FullName; if (!ctx.IsPreview) { - result.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); - result.Description = category.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); - result.BottomDescription = category.GetLocalized(x => x.BottomDescription, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaDescription = category.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaTitle = category.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + result.SeName = ctx.UrlRecords[nameof(Category)].GetSlug(ctx.LanguageId, category.Id); + result.Description = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.Description)) ?? category.Description; + result.BottomDescription = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.BottomDescription)) ?? category.BottomDescription; + result.MetaKeywords = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.MetaKeywords)) ?? category.MetaKeywords; + result.MetaDescription = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.MetaDescription)) ?? category.MetaDescription; + result.MetaTitle = translations.GetValue(ctx.LanguageId, category.Id, nameof(category.MetaTitle)) ?? category.MetaTitle; result._CategoryTemplateViewPath = ctx.CategoryTemplates.ContainsKey(category.CategoryTemplateId) ? ctx.CategoryTemplates[category.CategoryTemplateId] : ""; - result._Localized = GetLocalized(ctx, category, + result._Localized = GetLocalized(ctx, translations, urlRecords, category, x => x.Name, x => x.FullName, x => x.Description, @@ -564,12 +635,18 @@ private dynamic ToDynamic(DataExporterContext ctx, Category category) private dynamic ToDynamic(DataExporterContext ctx, Product product, string seName = null) { - if (product == null) - return null; + if (product == null) + { + return null; + } dynamic result = new DynamicEntity(product); + var translations = ctx.TranslationsPerPage[nameof(Product)]; + var urlRecords = ctx.UrlRecordsPerPage[nameof(Product)]; + var localizedName = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.Name)) ?? product.Name; - result.AppliedDiscounts = null; + result.AppliedDiscounts = null; + result.Downloads = null; result.TierPrices = null; result.ProductAttributes = null; result.ProductAttributeCombinations = null; @@ -579,30 +656,45 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, string seNam result.ProductTags = null; result.ProductSpecificationAttributes = null; result.ProductBundleItems = null; - - result.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + result.Name = localizedName; if (!ctx.IsPreview) { - result.SeName = seName ?? product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); - result.ShortDescription = product.GetLocalized(x => x.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); - result.FullDescription = product.GetLocalized(x => x.FullDescription, ctx.Projection.LanguageId ?? 0, true, false, true); - result.MetaKeywords = product.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaDescription = product.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); - result.MetaTitle = product.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); - result.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, ctx.Projection.LanguageId ?? 0, true, false); + result.SeName = seName ?? ctx.UrlRecordsPerPage[nameof(Product)].GetSlug(ctx.LanguageId, product.Id); + result.ShortDescription = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.ShortDescription)) ?? product.ShortDescription; + result.FullDescription = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.FullDescription)) ?? product.FullDescription; + result.MetaKeywords = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.MetaKeywords)) ?? product.MetaKeywords; + result.MetaDescription = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.MetaDescription)) ?? product.MetaDescription; + result.MetaTitle = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.MetaTitle)) ?? product.MetaTitle; + result.BundleTitleText = translations.GetValue(ctx.LanguageId, product.Id, nameof(product.BundleTitleText)) ?? product.BundleTitleText; result._ProductTemplateViewPath = ctx.ProductTemplates.ContainsKey(product.ProductTemplateId) ? ctx.ProductTemplates[product.ProductTemplateId] - : ""; + : string.Empty; + + // A standard, language independent base price formatting: / + var basePriceInfo = string.Empty; + + if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero) + { + var finalPrice = _priceCalculationService.Value.GetFinalPrice(product, ctx.ContextCustomer, true); + var productPrice = _taxService.Value.GetProductPrice(product, finalPrice, ctx.ContextCustomer, ctx.ContextCurrency, out var taxrate); + productPrice = _currencyService.Value.ConvertFromPrimaryStoreCurrency(productPrice, ctx.ContextCurrency); + + var priceIncludesTax = _services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax; + var priceTemplate = _services.Localization.GetResource("Products.BasePriceInfo.LanguageInsensitive"); + var formattedPriceBase = System.Convert.ToDecimal((productPrice / product.BasePriceAmount) * product.BasePriceBaseAmount); + var formattedPrice = _priceFormatter.Value.FormatPrice(formattedPriceBase, true, ctx.ContextCurrency, ctx.ContextLanguage, priceIncludesTax, false); - result._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, - _priceCalculationService.Value, ctx.ContextCustomer, ctx.ContextCurrency, decimal.Zero, true); + basePriceInfo = priceTemplate.FormatInvariant(formattedPrice, product.BasePriceBaseAmount, product.BasePriceMeasureUnit); + } + + result._BasePriceInfo = basePriceInfo; - ToDeliveryTime(ctx, result, product.DeliveryTimeId); + ToDeliveryTime(ctx, result, product.DeliveryTimeId); ToQuantityUnit(ctx, result, product.QuantityUnitId); - result._Localized = GetLocalized(ctx, product, + result._Localized = GetLocalized(ctx, translations, urlRecords, product, x => x.Name, x => x.ShortDescription, x => x.FullDescription, @@ -619,7 +711,6 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen { product.MergeWithCombination(productContext.Combination); - var languageId = ctx.Projection.LanguageId ?? 0; var numberOfPictures = ctx.Projection.NumberOfPictures ?? int.MaxValue; var productDetailsPictureSize = ctx.Projection.PictureSize > 0 ? ctx.Projection.PictureSize : _mediaSettings.Value.ProductDetailsPictureSize; @@ -644,7 +735,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); @@ -661,8 +755,9 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) { + var translations = ctx.TranslationsPerPage[nameof(ProductVariantAttributeValue)]; var valueNames = variantAttributeValues - .Select(x => x.GetLocalized(y => y.Name, languageId, true, false)) + .Select(x => translations.GetValue(ctx.LanguageId, x.Id, nameof(x.Name)) ?? x.Name) .ToList(); dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " "); @@ -701,7 +796,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, " > "); } } @@ -804,7 +899,6 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen .Select(x => { dynamic dyn = new DynamicEntity(x); - return dyn; }) .ToList(); @@ -819,14 +913,25 @@ 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); + var translations = ctx.TranslationsPerPage[nameof(ProductTag)]; + var localizedName = translations.GetValue(ctx.LanguageId, x.Id, nameof(x.Name)) ?? x.Name; - dyn.Name = x.GetLocalized(y => y.Name, languageId, true, false); - dyn.SeName = x.GetSeName(languageId); - dyn._Localized = GetLocalized(ctx, x, y => y.Name); + dyn.Name = localizedName; + dyn.SeName = SeoExtensions.GetSeName(localizedName, _seoSettings.Value); + dyn._Localized = GetLocalized(ctx, translations, null, x, y => y.Name); return dyn; }) @@ -844,10 +949,11 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen .Select(x => { dynamic dyn = new DynamicEntity(x); + var translations = ctx.TranslationsPerPage[nameof(ProductBundleItem)]; - dyn.Name = x.GetLocalized(y => y.Name, languageId, true, false); - dyn.ShortDescription = x.GetLocalized(y => y.ShortDescription, languageId, true, false); - dyn._Localized = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription); + dyn.Name = translations.GetValue(ctx.LanguageId, x.Id, nameof(x.Name)) ?? x.Name; + dyn.ShortDescription = translations.GetValue(ctx.LanguageId, x.Id, nameof(x.ShortDescription)) ?? x.ShortDescription; + dyn._Localized = GetLocalized(ctx, translations, null, x, y => y.Name, y => y.ShortDescription); return dyn; }) @@ -868,11 +974,16 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen string brand = null; var productManus = ctx.ProductExportContext.ProductManufacturers.GetOrLoad(product.Id); - if (productManus != null && productManus.Any()) - brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); - - if (brand.IsEmpty()) - brand = ctx.Projection.Brand; + if (productManus != null && productManus.Any()) + { + var translations = ctx.Translations[nameof(Manufacturer)]; + var manufacturer = productManus.First().Manufacturer; + brand = translations.GetValue(ctx.LanguageId, manufacturer.Id, nameof(manufacturer.Name)) ?? manufacturer.Name; + } + if (brand.IsEmpty()) + { + brand = ctx.Projection.Brand; + } dynObject._Brand = brand; } @@ -905,7 +1016,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen if (ctx.Supports(ExportFeatures.OffersShippingTimeFallback)) { dynamic deliveryTime = dynObject.DeliveryTime; - dynObject._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); + dynObject._ShippingTime = deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name; } if (ctx.Supports(ExportFeatures.OffersShippingCostsFallback)) @@ -979,15 +1090,17 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, bool isParen private dynamic ToDynamic(DataExporterContext ctx, Order order) { - if (order == null) - return null; + if (order == null) + { + return null; + } dynamic result = new DynamicEntity(order); result.OrderNumber = order.GetOrderNumber(); - result.OrderStatus = order.OrderStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); - result.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); - result.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); + result.OrderStatus = order.OrderStatus.GetLocalizedEnum(_services.Localization, ctx.LanguageId); + result.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_services.Localization, ctx.LanguageId); + result.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_services.Localization, ctx.LanguageId); result.Customer = null; result.BillingAddress = null; @@ -1008,13 +1121,14 @@ private dynamic ToDynamic(DataExporterContext ctx, Order order) private dynamic ToDynamic(DataExporterContext ctx, OrderItem orderItem) { - if (orderItem == null) - return null; + if (orderItem == null) + { + return null; + } dynamic result = new DynamicEntity(orderItem); orderItem.Product.MergeWithCombination(orderItem.AttributesXml, _productAttributeParser.Value); - result.Product = ToDynamic(ctx, orderItem.Product); return result; @@ -1022,8 +1136,10 @@ private dynamic ToDynamic(DataExporterContext ctx, OrderItem orderItem) private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) { - if (shipment == null) - return null; + if (shipment == null) + { + return null; + } dynamic result = new DynamicEntity(shipment); @@ -1031,7 +1147,6 @@ private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) .Select(x => { dynamic exp = new DynamicEntity(x); - return exp; }) .ToList(); @@ -1041,41 +1156,56 @@ private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) private dynamic ToDynamic(DataExporterContext ctx, Discount discount) { - if (discount == null) - return null; + if (discount == null) + { + return null; + } dynamic result = new DynamicEntity(discount); - return result; } - private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) - { - if (psa == null) - return null; + private dynamic ToDynamic(DataExporterContext ctx, Download download) + { + if (download == null) + { + return null; + } - var option = psa.SpecificationAttributeOption; + dynamic result = new DynamicEntity(download); + return result; + } - dynamic result = new DynamicEntity(psa); + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) + { + if (psa == null) + { + return null; + } - dynamic dynAttribute = new DynamicEntity(option.SpecificationAttribute); + var option = psa.SpecificationAttributeOption; + var attribute = option.SpecificationAttribute; - dynAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - dynAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); + dynamic result = new DynamicEntity(psa); + dynamic dynAttribute = new DynamicEntity(attribute); + var saTranslations = ctx.TranslationsPerPage[nameof(SpecificationAttribute)]; + var saoTranslations = ctx.TranslationsPerPage[nameof(SpecificationAttributeOption)]; - dynAttribute.Alias = option.SpecificationAttribute.GetLocalized(x => x.Alias, ctx.Projection.LanguageId ?? 0, true, false); - dynAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Alias); + dynAttribute.Name = saTranslations.GetValue(ctx.LanguageId, attribute.Id, nameof(attribute.Name)) ?? attribute.Name; + dynAttribute._Localized = GetLocalized(ctx, saTranslations, null, attribute, x => x.Name); - dynamic dynOption = new DynamicEntity(option); + dynAttribute.Alias = saTranslations.GetValue(ctx.LanguageId, attribute.Id, nameof(attribute.Alias)) ?? attribute.Alias; + dynAttribute._Localized = GetLocalized(ctx, saTranslations, null, attribute, x => x.Alias); - dynOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - dynOption._Localized = GetLocalized(ctx, option, x => x.Name); + dynamic dynOption = new DynamicEntity(option); - dynOption.Alias = option.GetLocalized(x => x.Alias, ctx.Projection.LanguageId ?? 0, true, false); - dynOption._Localized = GetLocalized(ctx, option, x => x.Alias); + dynOption.Name = saoTranslations.GetValue(ctx.LanguageId, option.Id, nameof(option.Name)) ?? option.Name; + dynOption._Localized = GetLocalized(ctx, saoTranslations, null, option, x => x.Name); - dynOption.SpecificationAttribute = dynAttribute; + dynOption.Alias = saoTranslations.GetValue(ctx.LanguageId, option.Id, nameof(option.Alias)) ?? option.Alias; + dynOption._Localized = GetLocalized(ctx, saoTranslations, null, option, x => x.Alias); + dynOption.SpecificationAttribute = dynAttribute; result.SpecificationAttributeOption = dynOption; return result; @@ -1083,18 +1213,21 @@ private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute private dynamic ToDynamic(DataExporterContext ctx, GenericAttribute genericAttribute) { - if (genericAttribute == null) - return null; + if (genericAttribute == null) + { + return null; + } dynamic result = new DynamicEntity(genericAttribute); - return result; } private dynamic ToDynamic(DataExporterContext ctx, NewsLetterSubscription subscription) { - if (subscription == null) - return null; + if (subscription == null) + { + return null; + } dynamic result = new DynamicEntity(subscription); @@ -1107,8 +1240,10 @@ private dynamic ToDynamic(DataExporterContext ctx, NewsLetterSubscription subscr private dynamic ToDynamic(DataExporterContext ctx, ShoppingCartItem shoppingCartItem) { - if (shoppingCartItem == null) - return null; + if (shoppingCartItem == null) + { + return null; + } dynamic result = new DynamicEntity(shoppingCartItem); @@ -1129,7 +1264,8 @@ private List Convert(DataExporterContext ctx, Product product) { var result = new List(); var productContext = new DynamicProductContext(); - productContext.SeName = product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); + + productContext.SeName = ctx.UrlRecordsPerPage[nameof(Product)].GetSlug(ctx.LanguageId, product.Id); productContext.Combinations = ctx.ProductExportContext.AttributeCombinations.GetOrLoad(product.Id); productContext.AbsoluteProductUrl = _productUrlHelper.Value.GetAbsoluteProductUrl( @@ -1353,24 +1489,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/ExportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs index 0673598fdc..cb44a39618 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs @@ -38,23 +38,21 @@ internal interface IExportDataSegmenterProvider : IExportDataSegmenterConsumer, public class ExportDataSegmenter : IExportDataSegmenterProvider where T : BaseEntity { - private Func> _load; - private Action> _loaded; - private Func> _convert; + private readonly Func> _load; + private readonly Action> _loaded; + private readonly Func> _convert; - private int _offset; - private int _take; - private int _limit; - private int _recordsPerSegment; - private int _totalRecords; + private readonly int _offset; + private readonly int _take; + private readonly int _limit; + private readonly int _recordsPerSegment; + private readonly int _totalRecords; - private int _skip; - private int _countRecords; - - private Queue _data; + private Queue _data; + private bool _endOfData; public ExportDataSegmenter( - Func> load, + Func> load, Action> loaded, Func> convert, int offset, @@ -71,61 +69,64 @@ public ExportDataSegmenter( _limit = limit; _recordsPerSegment = recordsPerSegment; _totalRecords = totalRecords; - - _skip = _offset; } /// - /// Total number of records + /// Total number of records. /// public int TotalRecords { get { - var total = Math.Max(_totalRecords - _offset, 0); + var total = Math.Max(_totalRecords - _offset, 0); - if (_limit > 0 && _limit < total) - return _limit; + if (_limit > 0 && _limit < total) + { + return _limit; + } return total; } } - /// - /// Number of processed records - /// - public int RecordCount - { - get { return _countRecords; } - } + /// + /// Number of processed records. + /// + public int RecordCount { get; private set; } - /// - /// Record per segment counter - /// - public int RecordPerSegmentCount { get; set; } + /// + /// Record per segment counter. + /// + public int RecordPerSegmentCount { get; set; } /// - /// Whether there is data available + /// Whether there is data available. /// public bool HasData { get { - if (_limit > 0 && _countRecords >= _limit) - return false; + if (_limit > 0 && RecordCount >= _limit) + { + return false; + } - if (_data != null && _data.Count > 0) - return true; + if (_data != null && _data.Count > 0) + { + return true; + } - if (_skip >= _totalRecords) - return false; + if (_endOfData) + { + return false; + } return true; } } /// - /// Gets current data segment + /// Gets current data segment. /// public IReadOnlyCollection CurrentSegment { @@ -138,68 +139,83 @@ public IReadOnlyCollection CurrentSegment { _convert(entity).Each(x => records.Add(x)); - if (++_countRecords >= _limit && _limit > 0) - return records; + if (++RecordCount >= _limit && _limit > 0) + { + return records; + } - if (++RecordPerSegmentCount >= _recordsPerSegment && _recordsPerSegment > 0) - return records; + if (++RecordPerSegmentCount >= _recordsPerSegment && _recordsPerSegment > 0) + { + return records; + } } return records; } } - /// - /// Reads the next segment - /// - /// - public bool ReadNextSegment() + /// + /// Read next segment. + /// + /// true next segment available. false no more data. + public bool ReadNextSegment() { - if (_limit > 0 && _countRecords >= _limit) - return false; - - if (_recordsPerSegment > 0 && RecordPerSegmentCount >= _recordsPerSegment) - return false; - - // do not make the queue longer than necessary - if (_recordsPerSegment > 0 && _data != null && _data.Count >= _recordsPerSegment) - return true; - - if (_skip >= _totalRecords) - return false; - - if (_data != null) - _skip += _take; - - if (_data != null && _data.Count > 0) - { - var data = new List(_data); - data.AddRange(_load(_skip)); - - _data = new Queue(data); - } - else - { - _data = new Queue(_load(_skip)); - } - - // give provider the opportunity to make something with entity ids + if (_limit > 0 && RecordCount >= _limit) + { + return false; + } + + if (_recordsPerSegment > 0 && RecordPerSegmentCount >= _recordsPerSegment) + { + return false; + } + + // Do not make the queue longer than necessary. + if (_recordsPerSegment > 0 && _data != null && _data.Count >= _recordsPerSegment) + { + return true; + } + + var newData = _load(); + + if (_data != null && _data.Count > 0) + { + var data = new List(_data); + if (newData != null) + { + data.AddRange(newData); + } + + _data = new Queue(data); + } + else + { + if (newData == null) + { + // End of data reached. + _endOfData = true; + return false; + } + + _data = new Queue(newData); + } + + // Give provider the opportunity to make something with entity ids. _loaded?.Invoke(_data.AsReadOnly()); - return (_data.Count > 0); + return _data.Count > 0; } /// - /// Dispose and reset segmenter instance + /// Dispose and reset segmenter instance. /// public void Dispose() { - if (_data != null) - _data.Clear(); - - _skip = _offset; - _countRecords = 0; + RecordCount = 0; RecordPerSegmentCount = 0; - } + + _endOfData = false; + _data?.Clear(); + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 27c5e3aa9b..f28ec5b11f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -9,7 +9,7 @@ namespace SmartStore.Services.DataExchange.Export { - public class ExportExecuteContext + public class ExportExecuteContext { private DataExportResult _result; private CancellationToken _cancellation; @@ -117,10 +117,15 @@ public bool IsMaxFailures /// public int MaxFileNameLength { get; internal set; } - /// - /// The name of the current export file - /// - public string FileName { get; internal set; } + /// + /// Index of current export file. + /// + public int FileIndex { get; internal set; } + + /// + /// The name of the current export file + /// + public string FileName { get; internal set; } /// /// The path of the export content folder @@ -216,20 +221,25 @@ public void SetProgress(string message) public class ExportDataUnit { - /// - /// Your Id to identify this stream within a list of streams - /// - public string Id { get; set; } + /// + /// Your Id to identify this stream within a list of streams + /// + public string Id { get; set; } /// /// Stream used to write data to /// public Stream DataStream { get; internal set; } - /// - /// The name of the file to be created - /// - public string FileName { get; set; } + /// + /// The related entity type. + /// + public RelatedEntityType? RelatedType { get; set; } + + /// + /// The name of the file to be created + /// + public string FileName { get; set; } /// /// Short optional text that describes the content of the file @@ -240,5 +250,10 @@ public class ExportDataUnit /// Whether to display the file in the profile file dialog /// public bool DisplayInFileDialog { get; set; } - } + + /// + /// Number of successful processed records. + /// + public int RecordsSucceeded { get; set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 0371702eec..4c9fd6fdb1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -15,7 +15,7 @@ namespace SmartStore.Services.DataExchange.Export { - public static class ExportExtensions + public static class ExportExtensions { /// /// Returns a value indicating whether the export provider is valid @@ -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"); } @@ -134,17 +138,22 @@ public static string ResolveFileNamePattern(this ExportProfile profile, Store st sb.Replace("%Store.Id%", store.Id.ToString()); sb.Replace("%File.Index%", fileIndex.ToString("D4")); - if (profile.FileNamePattern.Contains("%Profile.SeoName%")) - sb.Replace("%Profile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")); - - if (profile.FileNamePattern.Contains("%Store.SeoName%")) - sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores"); - - if (profile.FileNamePattern.Contains("%Random.Number%")) - sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString()); - - if (profile.FileNamePattern.Contains("%Timestamp%")) - sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture)); + if (profile.FileNamePattern.Contains("%Profile.SeoName%")) + { + sb.Replace("%Profile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false, false).Replace("-", "")); + } + if (profile.FileNamePattern.Contains("%Store.SeoName%")) + { + sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false, true) : "allstores"); + } + if (profile.FileNamePattern.Contains("%Random.Number%")) + { + sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString()); + } + if (profile.FileNamePattern.Contains("%Timestamp%")) + { + sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture)); + } var result = sb.ToString() .ToValidFileName("") @@ -183,7 +192,7 @@ public static string GetDeploymentFolder(this ExportDeployment deployment, bool } else { - path = FileSystemHelper.ValidateRootPath(deployment.FileSystemPath); + path = PathHelper.NormalizeAppRelativePath(deployment.FileSystemPath); path = CommonHelper.MapPath(path); } } @@ -241,17 +250,17 @@ public static string GetIconClass(this ExportDeploymentType type) switch (type) { case ExportDeploymentType.FileSystem: - return "fa-folder-open-o"; + return "far fa-folder-open"; case ExportDeploymentType.Email: - return "fa-envelope-o"; + return "far fa-envelope"; case ExportDeploymentType.Http: - return "fa-globe"; + return "fa fa-globe"; case ExportDeploymentType.Ftp: - return "fa-files-o"; + return "far fa-copy"; case ExportDeploymentType.PublicFolder: - return "fa-unlock"; + return "fa fa-unlock"; default: - return "fa-question"; + return "fa fa-question"; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index ed71ce4a8b..e3a4f541b6 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"); @@ -111,14 +110,14 @@ public virtual ExportProfile InsertExportProfile( } else { - // what we do here is to preset typical settings for feed creation - // but on the other hand they may be untypical for generic data export\exchange + // What we do here is to preset typical settings for feed creation + // but on the other hand they may be untypical for generic data export\exchange. var projection = new ExportProjection { RemoveCriticalCharacters = true, CriticalCharacters = "¼,½,¾", PriceType = PriceDisplayType.PreSelectedPrice, - NoGroupedProducts = (features.HasFlag(ExportFeatures.CanOmitGroupedProducts) ? true : false), + NoGroupedProducts = features.HasFlag(ExportFeatures.CanOmitGroupedProducts) ? true : false, OnlyIndividuallyVisibleAssociated = true, DescriptionMerging = ExportDescriptionMerging.Description }; @@ -149,7 +148,7 @@ public virtual ExportProfile InsertExportProfile( .Replace("/", "") .Replace("-", ""); - var folderName = SeoHelper.GetSeName(cleanedSystemName, true, false) + var folderName = SeoHelper.GetSeName(cleanedSystemName, true, false, false) .ToValidPath() .Truncate(_dataExchangeSettings.MaxFileNameLength); @@ -222,23 +221,21 @@ public virtual ExportProfile InsertExportProfile( public virtual void UpdateExportProfile(ExportProfile profile) { - if (profile == null) - throw new ArgumentNullException("profile"); + Guard.NotNull(profile, nameof(profile)); - profile.FolderName = FileSystemHelper.ValidateRootPath(profile.FolderName); + profile.FolderName = PathHelper.NormalizeAppRelativePath(profile.FolderName); - if (profile.FolderName == "~/") - { - throw new SmartException("Invalid export folder name."); - } + if (!PathHelper.IsSafeAppRootPath(profile.FolderName)) + { + throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.FolderName.Validate")); + } _exportProfileRepository.Update(profile); } public virtual void DeleteExportProfile(ExportProfile profile, bool force = false) { - if (profile == null) - throw new ArgumentNullException("profile"); + Guard.NotNull(profile, nameof(profile)); if (!force && profile.IsSystemProfile) throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.CannotDeleteSystemProfile")); @@ -246,7 +243,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..4af3ac1836 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); @@ -317,7 +318,8 @@ public void WriteQuantityUnit(dynamic quantityUnit, string node) _writer.Write("Id", entity.Id.ToString()); _writer.Write("Name", (string)quantityUnit.Name); - _writer.Write("Description", (string)quantityUnit.Description); + _writer.Write("NamePlural", (string)quantityUnit.NamePlural); + _writer.Write("Description", (string)quantityUnit.Description); _writer.Write("DisplayLocale", entity.DisplayLocale); _writer.Write("DisplayOrder", entity.DisplayOrder.ToString()); _writer.Write("IsDefault", entity.IsDefault.ToString()); @@ -501,7 +503,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 +531,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 +565,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 +607,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"); @@ -720,7 +753,6 @@ public void WriteProduct(dynamic product, string node) _writer.Write("BasePriceAmount", entityPvac.BasePriceAmount.HasValue ? entityPvac.BasePriceAmount.Value.ToString(_culture) : ""); _writer.Write("BasePriceBaseAmount", entityPvac.BasePriceBaseAmount.HasValue ? entityPvac.BasePriceBaseAmount.Value.ToString() : ""); _writer.Write("AssignedPictureIds", entityPvac.AssignedPictureIds); - _writer.Write("DeliveryTimeId", entityPvac.DeliveryTimeId.HasValue ? entityPvac.DeliveryTimeId.Value.ToString() : ""); _writer.Write("IsActive", entityPvac.IsActive.ToString()); WriteDeliveryTime(combination.DeliveryTime, "DeliveryTime"); @@ -910,7 +942,15 @@ public void WriteCustomer(dynamic customer, string node) _writer.Write("CreatedOnUtc", entity.CreatedOnUtc.ToString(_culture)); _writer.Write("LastLoginDateUtc", entity.LastLoginDateUtc.HasValue ? entity.LastLoginDateUtc.Value.ToString(_culture) : ""); _writer.Write("LastActivityDateUtc", entity.LastActivityDateUtc.ToString(_culture)); - _writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); + //_writer.Write("Salutation", entity.Salutation); // TODO: marked as "for future use" + _writer.Write("Title", entity.Title); + _writer.Write("FirstName", entity.FirstName); + _writer.Write("LastName", entity.LastName); + _writer.Write("FullName", entity.FullName); + _writer.Write("Company", entity.Company); + _writer.Write("CustomerNumber", entity.CustomerNumber); + _writer.Write("BirthDate", entity.BirthDate.HasValue ? entity.BirthDate.Value.ToString(_culture) : ""); + _writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); if (customer.CustomerRoles != null) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs index 3bea8233e3..b014d487f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs @@ -14,9 +14,7 @@ public interface IDataExporter { DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken); - IList Preview(DataExportRequest request, int pageIndex, int? totalRecords = null); - - int GetDataCount(DataExportRequest request); + DataExportPreviewResult Preview(DataExportRequest request, int pageIndex); /// /// Creates a product export context for fast retrieval (eager loading) of product navigation properties diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index c0e56b6175..317d4400e3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -2,17 +2,17 @@ using System.Linq; using System.Threading; using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Export.Internal { - internal class DataExporterContext + internal class DataExporterContext { public DataExporterContext( DataExportRequest request, @@ -40,14 +40,18 @@ public DataExporterContext( ProductTemplates = new Dictionary(); CategoryTemplates = new Dictionary(); NewsletterSubscriptions = new HashSet(); + Translations = new Dictionary(); + TranslationsPerPage = new Dictionary(); + UrlRecords = new Dictionary(); + UrlRecordsPerPage = new Dictionary(); - RecordsPerStore = new Dictionary(); + StatsPerStore = new Dictionary(); EntityIdsLoaded = new List(); - EntityIdsPerSegment = new List(); + EntityIdsPerSegment = new HashSet(); Result = new DataExportResult { - FileFolder = (IsFileBasedExport ? FolderContent : null) + FileFolder = IsFileBasedExport ? FolderContent : null }; ExecuteContext = new ExportExecuteContext(Result, CancellationToken, FolderContent); @@ -62,7 +66,7 @@ public DataExporterContext( } /// - /// All entity identifiers per export + /// All entity identifiers per export. /// public List EntityIdsLoaded { get; set; } public void SetLoadedEntityIds(IEnumerable ids) @@ -74,21 +78,22 @@ public void SetLoadedEntityIds(IEnumerable ids) } /// - /// All entity identifiers per segment (to avoid exporting products multiple times) + /// All entity identifiers per segment (to avoid exporting products multiple times). /// - public List EntityIdsPerSegment { get; set; } + public HashSet EntityIdsPerSegment { get; set; } + public int LastId { get; set; } - public int RecordCount { get; set; } - public Dictionary RecordsPerStore { get; set; } - public string ProgressInfo { get; set; } + public string ProgressInfo { get; set; } + public int RecordCount { get; set; } + public Dictionary StatsPerStore { get; set; } - public DataExportRequest Request { get; private set; } + public DataExportRequest Request { get; private set; } public CancellationToken CancellationToken { get; private set; } public bool IsPreview { get; private set; } public bool Supports(ExportFeatures feature) { - return (!IsPreview && Request.Provider.Metadata.ExportFeatures.HasFlag(feature)); + return !IsPreview && Request.Provider.Metadata.ExportFeatures.HasFlag(feature); } public ExportFilter Filter { get; private set; } @@ -96,8 +101,9 @@ public bool Supports(ExportFeatures feature) public Currency ContextCurrency { get; set; } public Customer ContextCustomer { get; set; } public Language ContextLanguage { get; set; } + public int LanguageId => Projection.LanguageId ?? 0; - public TraceLogger Log { get; set; } + public TraceLogger Log { get; set; } public Store Store { get; set; } public string FolderContent { get; private set; } @@ -107,7 +113,7 @@ public bool IsFileBasedExport get { return Request.Provider == null || Request.Provider.Value == null || Request.Provider.Value.FileExtension.HasValue(); } } - // data loaded once per export + // Data loaded once per export. public Dictionary DeliveryTimes { get; set; } public Dictionary QuantityUnits { get; set; } public Dictionary Stores { get; set; } @@ -117,14 +123,34 @@ public bool IsFileBasedExport public Dictionary CategoryTemplates { get; set; } public HashSet NewsletterSubscriptions { get; set; } - // data loaded once per page - public ProductExportContext ProductExportContext { get; set; } - public OrderExportContext OrderExportContext { get; set; } + /// + /// All translations for global scopes (like Category, Manufacturer etc.) + /// + public Dictionary Translations { get; set; } + public Dictionary UrlRecords { get; set; } + + // Data loaded once per page. + public ProductExportContext ProductExportContext { get; set; } + public ProductExportContext AssociatedProductContext { get; set; } + public OrderExportContext OrderExportContext { get; set; } public ManufacturerExportContext ManufacturerExportContext { get; set; } public CategoryExportContext CategoryExportContext { get; set; } public CustomerExportContext CustomerExportContext { get; set; } - public ExportExecuteContext ExecuteContext { get; set; } + /// + /// All per page translations (like ProductVariantAttributeValue etc.) + /// + public Dictionary TranslationsPerPage { get; set; } + public Dictionary UrlRecordsPerPage { get; set; } + + public ExportExecuteContext ExecuteContext { get; set; } public DataExportResult Result { get; set; } } + + internal class RecordStats + { + public int TotalRecords { get; set; } + public int MaxId { get; set; } + public int OffsetId { get; set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ProductExportContext.cs index 1cdfb837be..5a3b22a4bd 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, @@ -34,9 +35,11 @@ public ProductExportContext( Func> productManufacturers, Func> appliedDiscounts, Func> productBundleItems, - Func> pictures, + Func> associatedProducts, + Func> pictures, Func> productPictures, - Func> productTags) + Func> productTags, + Func> downloads) : base(products, attributes, attributeCombinations, @@ -44,24 +47,23 @@ public ProductExportContext( productCategories, productManufacturers, appliedDiscounts, - productBundleItems) + productBundleItems, + associatedProducts) { _funcPictures = pictures; _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(); + _productPictures?.Clear(); + _productTags?.Clear(); + _specificationAttributes?.Clear(); + _pictures?.Clear(); + _downloads?.Clear(); base.Clear(); } @@ -113,5 +115,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/ISyncMappingService.cs b/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs index 9d08368ae3..9bdbc6d790 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ISyncMappingService.cs @@ -103,7 +103,7 @@ public static SyncMapping InsertSyncMapping(this ISyncMappingService svc, T e { ContextName = contextName, EntityId = entity.Id, - EntityName = typeof(T).Name, + EntityName = entity.GetEntityName(), SourceKey = sourceKey }; @@ -125,7 +125,7 @@ public static SyncMapping GetSyncMappingByEntity(this ISyncMappingService svc throw Error.InvalidOperation("Cannot get a sync mapping record for a transient (unsaved) entity"); } - return svc.GetSyncMappingByEntity(entity.Id, typeof(T).Name, contextName); + return svc.GetSyncMappingByEntity(entity.Id, entity.GetEntityName(), contextName); } /// 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/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index d195a4b259..e413acdde9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -89,35 +89,46 @@ private bool HasPermission(DataImporterContext ctx) return true; } - private void LogResult(DataImporterContext ctx) + private void LogResults(DataImporterContext ctx) { - var result = ctx.ExecuteContext.Result; - var sb = new StringBuilder(); - - sb.AppendLine(); - sb.AppendFormat("Started:\t\t{0}\r\n", result.StartDateUtc.ToLocalTime()); - sb.AppendFormat("Finished:\t\t{0}\r\n", result.EndDateUtc.ToLocalTime()); - sb.AppendFormat("Duration:\t\t{0}\r\n", (result.EndDateUtc - result.StartDateUtc).ToString("g")); - sb.AppendLine(); - sb.AppendFormat("Total rows:\t\t{0}\r\n", result.TotalRecords); - sb.AppendFormat("Rows processed:\t\t{0}\r\n", result.AffectedRecords); - sb.AppendFormat("Records imported:\t{0}\r\n", result.NewRecords); - sb.AppendFormat("Records updated:\t{0}\r\n", result.ModifiedRecords); - sb.AppendLine(); - sb.AppendFormat("Warnings:\t\t{0}\r\n", result.Warnings); - sb.AppendFormat("Errors:\t\t\t{0}", result.Errors); - - ctx.Log.Info(sb.ToString()); - - foreach (var message in result.Messages) - { - if (message.MessageType == ImportMessageType.Error) - ctx.Log.Error(new Exception(message.FullMessage), message.ToString()); - else if (message.MessageType == ImportMessageType.Warning) - ctx.Log.Warn(message.ToString()); - else - ctx.Log.Info(message.ToString()); - } + var sb = new StringBuilder(); + + foreach (var item in ctx.Results) + { + var result = item.Value; + var entityName = item.Key.HasValue() ? item.Key : ctx.Request.Profile.EntityType.ToString(); + + sb.Clear(); + sb.AppendLine(); + sb.AppendLine(new string('-', 40)); + sb.AppendLine("Object: " + entityName); + sb.AppendLine("Started: " + result.StartDateUtc.ToLocalTime()); + sb.AppendLine("Finished: " + result.EndDateUtc.ToLocalTime()); + sb.AppendLine("Duration: " + (result.EndDateUtc - result.StartDateUtc).ToString("g")); + sb.AppendLine("Rows total: " + result.TotalRecords); + sb.AppendLine("Rows processed: " + result.AffectedRecords); + sb.AppendLine("Rows imported: " + result.NewRecords); + sb.AppendLine("Rows updated: " + result.ModifiedRecords); + sb.AppendLine("Warnings: " + result.Warnings); + sb.Append("Errors: " + result.Errors); + ctx.Log.Info(sb.ToString()); + + foreach (var message in result.Messages) + { + if (message.MessageType == ImportMessageType.Error) + { + ctx.Log.Error(new Exception(message.FullMessage), message.ToString()); + } + else if (message.MessageType == ImportMessageType.Warning) + { + ctx.Log.Warn(message.ToString()); + } + else + { + ctx.Log.Info(message.ToString()); + } + } + } } private void SendCompletionEmail(DataImporterContext ctx) @@ -184,129 +195,128 @@ private void SendCompletionEmail(DataImporterContext ctx) //_services.DbContext.SaveChanges(); } - private void ImportCoreInner(DataImporterContext ctx, string filePath) + private void ImportCoreInner(DataImporterContext ctx, ImportFile file) { - if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard) - return; - - { - var logHead = new StringBuilder(); - logHead.AppendLine(); - logHead.AppendLine(new string('-', 40)); - logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.Append("Import profile:\t\t" + ctx.Request.Profile.Name); - logHead.AppendLine(ctx.Request.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Request.Profile.Id)); - - logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Profile.EntityType.ToString()); - logHead.AppendLine("File:\t\t\t" + Path.GetFileName(filePath)); - - var customer = _services.WorkContext.CurrentCustomer; - logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); - - ctx.Log.Info(logHead.ToString()); - } - - if (!File.Exists(filePath)) - { - throw new SmartException("File does not exist {0}.".FormatInvariant(filePath)); - } + var context = ctx.ExecuteContext; + var profile = ctx.Request.Profile; - CsvConfiguration csvConfiguration = null; - var extension = Path.GetExtension(filePath); + if (context.Abort == DataExchangeAbortion.Hard) + { + return; + } - if ((new string[] { ".csv", ".txt", ".tab" }).Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!File.Exists(file.Path)) { - var converter = new CsvConfigurationConverter(); - csvConfiguration = converter.ConvertFrom(ctx.Request.Profile.FileTypeConfiguration); + throw new SmartException($"File does not exist {file.Path}."); } - if (csvConfiguration == null) + using (var stream = new FileStream(file.Path, FileMode.Open, FileAccess.Read, FileShare.Read)) { - csvConfiguration = CsvConfiguration.ExcelFriendlyConfiguration; - } + var csvConfiguration = file.IsCsv + ? (new CsvConfigurationConverter().ConvertFrom(profile.FileTypeConfiguration) ?? CsvConfiguration.ExcelFriendlyConfiguration) + : CsvConfiguration.ExcelFriendlyConfiguration; - using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) - { - ctx.ExecuteContext.DataTable = LightweightDataTable.FromFile( - Path.GetFileName(filePath), + context.DataTable = LightweightDataTable.FromFile( + file.Name, stream, stream.Length, - csvConfiguration, - ctx.Request.Profile.Skip, - ctx.Request.Profile.Take > 0 ? ctx.Request.Profile.Take : int.MaxValue - ); - - try - { - ctx.Importer.Execute(ctx.ExecuteContext); - } - catch (Exception exception) - { - ctx.ExecuteContext.Abort = DataExchangeAbortion.Hard; - ctx.ExecuteContext.Result.AddError(exception, "The importer failed: {0}.".FormatInvariant(exception.ToAllMessages())); - } - - if (ctx.ExecuteContext.IsMaxFailures) - ctx.ExecuteContext.Result.AddWarning("Import aborted. The maximum number of failures has been reached."); - - if (ctx.CancellationToken.IsCancellationRequested) - ctx.ExecuteContext.Result.AddWarning("Import aborted. A cancellation has been requested."); + csvConfiguration, + profile.Skip, + profile.Take > 0 ? profile.Take : int.MaxValue); + + context.ColumnMap = file.RelatedType.HasValue ? new ColumnMap() : ctx.ColumnMap; + context.File = file; + + try + { + ctx.Importer.Execute(context); + } + catch (Exception ex) + { + context.Abort = DataExchangeAbortion.Hard; + context.Result.AddError(ex, $"The importer failed: {ex.ToAllMessages()}."); + } + finally + { + context.Result.EndDateUtc = DateTime.UtcNow; + + if (context.IsMaxFailures) + { + context.Result.AddWarning("Import aborted. The maximum number of failures has been reached."); + } + if (ctx.CancellationToken.IsCancellationRequested) + { + context.Result.AddWarning("Import aborted. A cancellation has been requested."); + } + } } } private void ImportCoreOuter(DataImporterContext ctx) { - if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) - return; - - var logPath = ctx.Request.Profile.GetImportLogPath(); - - FileSystemHelper.Delete(logPath); + var customer = _services.WorkContext.CurrentCustomer; + var profile = ctx.Request.Profile; + var logPath = profile.GetImportLogPath(); + FileSystemHelper.DeleteFile(logPath); using (var logger = new TraceLogger(logPath)) { - var scopes = new List(); + var scopes = new List(); - try + try { - _dbCache.Enabled = false; + var files = profile.GetImportFiles(profile.ImportRelatedData); + var groupedFiles = files.GroupBy(x => x.RelatedType); + + if (!files.Any()) + throw new SmartException("No files to import."); + + if (!HasPermission(ctx)) + throw new SmartException("You do not have permission to perform the selected import."); + + _dbCache.Enabled = false; scopes.Add(_localizedEntityService.BeginScope()); scopes.Add(_urlRecordService.BeginScope()); ctx.Log = logger; + ctx.Importer = _importerFactory(profile.EntityType); - ctx.ExecuteContext.Request = ctx.Request; + ctx.ExecuteContext.Request = ctx.Request; ctx.ExecuteContext.DataExchangeSettings = _dataExchangeSettings.Value; ctx.ExecuteContext.Services = _services; ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.Languages = _languageService.GetAllLanguages(true); - ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; - ctx.ExecuteContext.KeyFieldNames = ctx.Request.Profile.KeyFieldNames.SplitSafe(","); - ctx.ExecuteContext.ImportFolder = ctx.Request.Profile.GetImportFolder(); - ctx.ExecuteContext.ExtraData = XmlHelper.Deserialize(ctx.Request.Profile.ExtraData); - - { - var mapConverter = new ColumnMapConverter(); - ctx.ExecuteContext.ColumnMap = mapConverter.ConvertFrom(ctx.Request.Profile.ColumnMapping) ?? new ColumnMap(); - } - - var files = ctx.Request.Profile.GetImportFiles(); - - if (files.Count == 0) - throw new SmartException("No files to import."); - - if (!HasPermission(ctx)) - throw new SmartException("You do not have permission to perform the selected import."); - - ctx.Importer = _importerFactory(ctx.Request.Profile.EntityType); - - _services.EventPublisher.Publish(new ImportExecutingEvent(ctx.ExecuteContext)); - - files.ForEach(x => ImportCoreInner(ctx, x)); + ctx.ExecuteContext.UpdateOnly = profile.UpdateOnly; + ctx.ExecuteContext.KeyFieldNames = profile.KeyFieldNames.SplitSafe(","); + ctx.ExecuteContext.ImportFolder = profile.GetImportFolder(); + ctx.ExecuteContext.ExtraData = XmlHelper.Deserialize(profile.ExtraData); + + var sb = new StringBuilder(); + sb.AppendLine(); + sb.AppendLine(new string('-', 40)); + sb.AppendLine("SmartStore.NET: v." + SmartStoreVersion.CurrentFullVersion); + sb.AppendLine("Import profile: {0} {1}".FormatInvariant(profile.Name, profile.Id == 0 ? " (volatile)" : $" (Id {profile.Id})")); + foreach (var fileGroup in groupedFiles) + { + var entityName = fileGroup.Key.HasValue ? fileGroup.Key.Value.ToString() : profile.EntityType.ToString(); + var fileNames = string.Join(", ", fileGroup.Select(x => x.Name)); + sb.AppendLine("{0} files: {1}".FormatInvariant(entityName, fileNames)); + } + sb.Append("Executed by: " + customer.Email.NullEmpty() ?? customer.SystemName.NaIfEmpty()); + ctx.Log.Info(sb.ToString()); + + _services.EventPublisher.Publish(new ImportExecutingEvent(ctx.ExecuteContext)); + + foreach (var fileGroup in groupedFiles) + { + ctx.ExecuteContext.Result = ctx.Results[fileGroup.Key.HasValue ? fileGroup.Key.Value.ToString() : string.Empty] = new ImportResult(); + + fileGroup.Each(x => ImportCoreInner(ctx, x)); + } } - catch (Exception exception) + catch (Exception ex) { - ctx.ExecuteContext.Result.AddError(exception); + logger.ErrorsAll(ex); } finally { @@ -317,61 +327,64 @@ private void ImportCoreOuter(DataImporterContext ctx) _services.EventPublisher.Publish(new ImportExecutedEvent(ctx.ExecuteContext)); } - catch (Exception exception) + catch (Exception ex) { - ctx.ExecuteContext.Result.AddError(exception); - } + logger.ErrorsAll(ex); + } try { - // database context sharing problem: if there are entities in modified state left by the provider due to SaveChanges failure, + // Database context sharing problem: if there are entities in modified state left by the provider due to SaveChanges failure, // then all subsequent SaveChanges would fail too (e.g. IImportProfileService.UpdateImportProfile, IScheduledTaskService.UpdateTask...). // so whatever it is, detach\dispose all what the tracker still has tracked. _services.DbContext.DetachAll(false); } - catch (Exception exception) + catch (Exception ex) { - ctx.ExecuteContext.Result.AddError(exception); - } + logger.ErrorsAll(ex); + } try { SendCompletionEmail(ctx); } - catch (Exception exception) + catch (Exception ex) { - ctx.ExecuteContext.Result.AddError(exception); - } + logger.ErrorsAll(ex); + } try { - ctx.ExecuteContext.Result.EndDateUtc = DateTime.UtcNow; - LogResult(ctx); + LogResults(ctx); } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } try { - ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.ExecuteContext.Result.Clone()); - _importProfileService.UpdateImportProfile(ctx.Request.Profile); + if (ctx.Results.TryGetValue(string.Empty, out var result)) + { + profile.ResultInfo = XmlHelper.Serialize(result.Clone()); + _importProfileService.UpdateImportProfile(profile); + } } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } try { ctx.Request.CustomData.Clear(); - ctx.Log = null; + ctx.Results.Clear(); + ctx.Log = null; } - catch (Exception exception) + catch (Exception ex) { - logger.ErrorsAll(exception); + logger.ErrorsAll(ex); } } } @@ -382,11 +395,13 @@ public void Import(DataImportRequest request, CancellationToken cancellationToke Guard.NotNull(request, nameof(request)); Guard.NotNull(cancellationToken, nameof(cancellationToken)); - var ctx = new DataImporterContext(request, cancellationToken, T("Admin.DataExchange.Import.ProgressInfo")); - - ImportCoreOuter(ctx); + if (request.Profile != null && request.Profile.Enabled) + { + var ctx = new DataImporterContext(request, cancellationToken, T("Admin.DataExchange.Import.ProgressInfo")); + ImportCoreOuter(ctx); + } - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 3ad203cc4a..4452f00382 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -1,27 +1,26 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net.Mime; using System.Linq; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.IO; -using SmartStore.Utilities; using System.Linq.Expressions; -using SmartStore.Core; +using System.Net.Mime; using Autofac; -using SmartStore.Services.Localization; +using SmartStore.Core; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.IO; +using SmartStore.Services.Localization; using SmartStore.Services.Seo; -using SmartStore.Core.Data; using SmartStore.Services.Stores; -using SmartStore.Core.Domain.Stores; +using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter + public abstract class EntityImporterBase : IEntityImporter { - private const string _imageDownloadFolder = @"Content\DownloadedImages"; + private const string IMAGE_DOWNLOAD_FOLDER = @"Content\DownloadedImages"; public DateTime UtcNow { @@ -64,17 +63,18 @@ protected void Initialize(ImportExecuteContext context) { UtcNow = DateTime.UtcNow; DownloadedItems = new Dictionary(); - ImageDownloadFolder = Path.Combine(context.ImportFolder, _imageDownloadFolder); + ImageDownloadFolder = Path.Combine(context.ImportFolder, IMAGE_DOWNLOAD_FOLDER); var settings = context.DataExchangeSettings; - if (settings.ImageImportFolder.HasValue()) - ImageFolder = Path.Combine(context.ImportFolder, settings.ImageImportFolder); - else - ImageFolder = context.ImportFolder; + ImageFolder = settings.ImageImportFolder.HasValue() + ? Path.Combine(context.ImportFolder, settings.ImageImportFolder) + : context.ImportFolder; - if (!System.IO.Directory.Exists(ImageDownloadFolder)) - System.IO.Directory.CreateDirectory(ImageDownloadFolder); + if (!System.IO.Directory.Exists(ImageDownloadFolder)) + { + System.IO.Directory.CreateDirectory(ImageDownloadFolder); + } DownloaderContext = new FileDownloadManagerContext { @@ -86,7 +86,7 @@ protected void Initialize(ImportExecuteContext context) context.Result.TotalRecords = context.DataSegmenter.TotalRows; } - public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoName, int displayOrder) + public FileDownloadManagerItem CreateDownloadImage(ImportExecuteContext context, string urlOrPath, string seoName, int displayOrder) { var item = new FileDownloadManagerItem { @@ -102,10 +102,10 @@ public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoN if (urlOrPath.IsWebUrl()) { - item.Url = urlOrPath; - item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); + item.Url = context.Services.WebHelper.ModifyQueryString(urlOrPath, "q=100", null); + item.FileName = "{0}-{1}".FormatInvariant(seoName, item.Id).ToValidFileName(); - if (DownloadedItems.ContainsKey(urlOrPath)) + if (DownloadedItems.ContainsKey(urlOrPath)) { item.Path = Path.Combine(ImageDownloadFolder, DownloadedItems[urlOrPath]); item.Success = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportBatchExecutedEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportBatchExecutedEvent.cs new file mode 100644 index 0000000000..b903b505cc --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/Events/ImportBatchExecutedEvent.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using SmartStore.Core; + +namespace SmartStore.Services.DataExchange.Import.Events +{ + /// + /// An event that is fired after an import of a data batch. + /// + /// The entity type to be imported. + public class ImportBatchExecutedEvent where TEntity : BaseEntity + { + public ImportBatchExecutedEvent(ImportExecuteContext context, IEnumerable> batch) + { + Guard.NotNull(context, nameof(context)); + Guard.NotNull(batch, nameof(batch)); + + Context = context; + Batch = batch; + } + + /// + /// Context of the import. + /// + public ImportExecuteContext Context { get; private set; } + + /// + /// Current batch of import data. + /// + public IEnumerable> Batch { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 2593073b88..d5b3ec8692 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -6,7 +6,7 @@ namespace SmartStore.Services.DataExchange.Import { - public class ImportExecuteContext + public class ImportExecuteContext { private DataExchangeAbortion _abortion; private ProgressValueSetter _progressValueSetter; @@ -25,7 +25,6 @@ public ImportExecuteContext( CancellationToken = cancellation; CustomProperties = new Dictionary(); - Result = new ImportResult(); } public DataImportRequest Request @@ -68,10 +67,19 @@ public ColumnMap ColumnMap internal set; } - /// - /// Whether to only update existing records - /// - public bool UpdateOnly + /// + /// Infos about the import file. + /// + public ImportFile File + { + get; + internal set; + } + + /// + /// Whether to only update existing records + /// + public bool UpdateOnly { get; internal set; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs index 09201325f1..9b7918fa18 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExtensions.cs @@ -7,7 +7,7 @@ namespace SmartStore.Services.DataExchange.Import { - public static class ImportExtensions + public static class ImportExtensions { /// /// Get folder for import files @@ -39,31 +39,45 @@ public static string GetImportFolder( return path; } - /// - /// Gets import files for an import profile - /// - /// Import profile - /// List of file paths - public static List GetImportFiles(this ImportProfile profile) - { - var importFolder = profile.GetImportFolder(true); + /// + /// Gets import files for an import profile. + /// + /// Import profile. + /// Whether to include related data files. + /// List of import files. + public static List GetImportFiles(this ImportProfile profile, bool includeRelatedFiles = true) + { + var result = new List(); + var importFolder = profile.GetImportFolder(true); - if (System.IO.Directory.Exists(importFolder)) - { - return System.IO.Directory.EnumerateFiles(importFolder, "*", SearchOption.TopDirectoryOnly) - .OrderBy(x => x) - .ToList(); - } + if (System.IO.Directory.Exists(importFolder)) + { + var paths = System.IO.Directory.EnumerateFiles(importFolder, "*", SearchOption.TopDirectoryOnly); + foreach (var path in paths) + { + var file = new ImportFile(path); + if (!includeRelatedFiles && file.RelatedType.HasValue) + { + continue; + } - return new List(); - } + result.Add(file); + } - /// - /// Get log file path for an import profile - /// - /// Import profile - /// Log file path - public static string GetImportLogPath(this ImportProfile profile) + // Always main data files first. + result = result.OrderBy(x => x.RelatedType).ThenBy(x => x.Path).ToList(); + return result; + } + + return result; + } + + /// + /// Get log file path for an import profile + /// + /// Import profile + /// Log file path + public static string GetImportLogPath(this ImportProfile profile) { return Path.Combine(profile.GetImportFolder(), "log.txt"); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportFile.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportFile.cs new file mode 100644 index 0000000000..6b0af36ef2 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportFile.cs @@ -0,0 +1,64 @@ +using System; +using System.Linq; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Import +{ + public partial class ImportFile + { + public ImportFile(string path) + { + Guard.NotEmpty(path, nameof(path)); + + Path = path; + + var fileName = System.IO.Path.GetFileNameWithoutExtension(path); + if (fileName.HasValue()) + { + foreach (RelatedEntityType type in Enum.GetValues(typeof(RelatedEntityType))) + { + if (fileName.EndsWith(type.ToString(), StringComparison.OrdinalIgnoreCase)) + { + RelatedType = type; + } + } + } + } + + /// + /// Path of the import file. + /// + public string Path { get; private set; } + + /// + /// File name of . + /// + public string Name => System.IO.Path.GetFileName(Path); + + /// + /// File extension of . + /// + public string Extension => System.IO.Path.GetExtension(Path); + + /// + /// Indicates whether the file has an CSV file extension. + /// + internal bool IsCsv + { + get + { + return (new string[] { ".csv", ".txt", ".tab" }).Contains(Extension, StringComparer.OrdinalIgnoreCase); + } + } + + /// + /// Related entity type. + /// + public RelatedEntityType? RelatedType { get; private set; } + + /// + /// File label text. + /// + public string Label { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs index fbded791c7..da6ad91076 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -223,7 +223,7 @@ public virtual ImportProfile InsertImportProfile(string fileName, string name, I profile.KeyFieldNames = string.Join(",", keyFieldNames); - profile.FolderName = SeoHelper.GetSeName(name, true, false) + profile.FolderName = SeoHelper.GetSeName(name, true, false, false) .ToValidPath() .Truncate(_dataExchangeSettings.MaxFileNameLength); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs index a8a29366f7..a0de84eae2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportResult.cs @@ -171,26 +171,26 @@ public string LastError object ICloneable.Clone() { - return this.Clone(); + return this.MemberwiseClone(); } public SerializableImportResult Clone() { - var result = new SerializableImportResult(); - result.StartDateUtc = StartDateUtc; - result.EndDateUtc = EndDateUtc; - result.TotalRecords = TotalRecords; - result.SkippedRecords = SkippedRecords; - result.NewRecords = NewRecords; - result.ModifiedRecords = ModifiedRecords; - result.AffectedRecords = AffectedRecords; - result.Cancelled = Cancelled; - result.Warnings = Warnings; - result.Errors = Errors; - result.LastError = LastError; - - return result; - } + var result = new SerializableImportResult(); + result.StartDateUtc = StartDateUtc; + result.EndDateUtc = EndDateUtc; + result.TotalRecords = TotalRecords; + result.SkippedRecords = SkippedRecords; + result.NewRecords = NewRecords; + result.ModifiedRecords = ModifiedRecords; + result.AffectedRecords = AffectedRecords; + result.Cancelled = Cancelled; + result.Warnings = Warnings; + result.Errors = Errors; + result.LastError = LastError; + + return result; + } } 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/DataExchange/Import/Internal/DataImporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs index 4323a9ef90..f9f36594cc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/Internal/DataImporterContext.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.Collections.Generic; +using System.Threading; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Import.Internal @@ -13,16 +14,22 @@ public DataImporterContext( Request = request; CancellationToken = cancellationToken; - ExecuteContext = new ImportExecuteContext(CancellationToken, Request.ProgressValueSetter, progressInfo) { Request = request }; - } + ExecuteContext = new ImportExecuteContext(CancellationToken, Request.ProgressValueSetter, progressInfo) + { + Request = request + }; + + ColumnMap = new ColumnMapConverter().ConvertFrom(Request.Profile.ColumnMapping) ?? new ColumnMap(); + Results = new Dictionary(); + } public DataImportRequest Request { get; private set; } public CancellationToken CancellationToken { get; private set; } public TraceLogger Log { get; set; } - public ImportExecuteContext ExecuteContext { get; set; } - public IEntityImporter Importer { get; set; } - } + public ColumnMap ColumnMap { get; private set; } + public Dictionary Results { get; private set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs b/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs index 395a8f208a..8b455a6955 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/SyncMappingService.cs @@ -113,7 +113,7 @@ public void DeleteSyncMappingsFor(T entity) where T : BaseEntity return; } - _syncMappingsRepository.DeleteAll(x => x.EntityId == entity.Id && x.EntityName == typeof(T).Name); + _syncMappingsRepository.DeleteAll(x => x.EntityId == entity.Id && x.EntityName == entity.GetEntityName()); } public void DeleteSyncMappings(string contextName, string entityName = null) 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/Directory/GeoCountryLookup.cs b/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs index 497cd4a8a7..71ec189d47 100644 --- a/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs +++ b/src/Libraries/SmartStore.Services/Directory/GeoCountryLookup.cs @@ -1,123 +1,58 @@ using System; using System.Net; +using MaxMind.GeoIP2; using SmartStore.Core; using SmartStore.Core.Caching; +using SmartStore.Utilities; using SmDir = SmartStore.Core.Domain.Directory; namespace SmartStore.Services.Directory { - public partial class GeoCountryLookup : IGeoCountryLookup + public partial class GeoCountryLookup : DisposableObject, IGeoCountryLookup { - private readonly IWebHelper _webHelper; - private readonly ICountryService _countryService; - private readonly IRequestCache _requestCache; - private readonly ICacheManager _cache; + private readonly DatabaseReader _reader; + private readonly object _lock = new object(); - public GeoCountryLookup(IWebHelper webHelper, IRequestCache requestCache, ICacheManager cache, ICountryService countryService) + public GeoCountryLookup() { - this._webHelper = webHelper; - this._requestCache = requestCache; - this._cache = cache; - this._countryService = countryService; + _reader = new DatabaseReader(CommonHelper.MapPath("~/App_Data/GeoLite2/GeoLite2-Country.mmdb")); } - private MaxMind.GeoIP.LookupService GetLookupService() - { - return _cache.Get("GeoCountryLookup", () => - { - var lookupService = new MaxMind.GeoIP.LookupService(_webHelper.MapPath("~/App_Data/GeoIP.dat")); - return lookupService; - }); - } - - public virtual string LookupCountryCode(string str) + public LookupCountryResponse LookupCountry(string addr) { - if (String.IsNullOrEmpty(str)) - return string.Empty; - - IPAddress addr; - try + if (addr.HasValue() && IPAddress.TryParse(addr, out var ipAddress)) { - addr = IPAddress.Parse(str); + return LookupCountry(ipAddress); } - catch - { - return string.Empty; - } - return LookupCountryCode(addr); - } - public virtual string LookupCountryCode(IPAddress addr) - { - try - { - var lookupService = GetLookupService(); - var country = lookupService.getCountry(addr); - var code = country.getCode(); - if (code == "--") - return string.Empty; - - return code; - } - catch - { - return string.Empty; - } + return null; } - public virtual string LookupCountryName(string str) + public LookupCountryResponse LookupCountry(IPAddress addr) { - if (String.IsNullOrEmpty(str)) - return string.Empty; + Guard.NotNull(addr, nameof(addr)); - IPAddress addr; - try + if (_reader.TryCountry(addr, out var response) && response.Country != null) { - addr = IPAddress.Parse(str); + var country = response.Country; + return new LookupCountryResponse + { + GeoNameId = country.GeoNameId, + IsoCode = country.IsoCode, + Name = country.Name, + IsInEu = country.IsInEuropeanUnion + }; } - catch - { - return string.Empty; - } - return LookupCountryName(addr); + + return null; } - public virtual string LookupCountryName(IPAddress addr) + protected override void OnDispose(bool disposing) { - try - { - var lookupService = GetLookupService(); - var country = lookupService.getCountry(addr); - return country.getName(); - } - catch - { - return string.Empty; - } + if (disposing && _reader != null) + { + _reader.Dispose(); + } } - - public virtual bool IsEuIpAddress(string ipAddress, out SmDir.Country euCountry) - { - euCountry = null; - - if (ipAddress.IsEmpty()) - return false; - - euCountry = _requestCache.Get("GeoCountryLookup.EuCountry.{0}".FormatInvariant(ipAddress), () => - { - var countryCode = LookupCountryCode(ipAddress); - if (countryCode.IsEmpty()) - return (SmDir.Country)null; - - var country = _countryService.GetCountryByTwoLetterIsoCode(countryCode); - return country; - }); - - if (euCountry == null) - return false; - - return euCountry.SubjectToVat; - } - } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/IGeoCountryLookup.cs b/src/Libraries/SmartStore.Services/Directory/IGeoCountryLookup.cs index 815a030b2c..cfe4cae186 100644 --- a/src/Libraries/SmartStore.Services/Directory/IGeoCountryLookup.cs +++ b/src/Libraries/SmartStore.Services/Directory/IGeoCountryLookup.cs @@ -3,27 +3,36 @@ namespace SmartStore.Services.Directory { - /// - /// Country lookup helper - /// - public partial interface IGeoCountryLookup + public sealed class LookupCountryResponse { - string LookupCountryCode(string str); + /// + /// The GeoName ID for the country. + /// + public int? GeoNameId { get; set; } - string LookupCountryCode(IPAddress addr); + /// + /// The english name of the country. + /// + public string Name { get; set; } - string LookupCountryName(string str); + /// + /// The two-letter ISO 3166-1 alpha code for the country + /// + public string IsoCode { get; set; } + + /// + /// This is true if the country is a member state of the European Union. + /// + public bool IsInEu { get; set; } + } - string LookupCountryName(IPAddress addr); + /// + /// Country lookup helper for IPv4/6 addresses + /// + public partial interface IGeoCountryLookup + { + LookupCountryResponse LookupCountry(string addr); - /// - /// Gets a value indicating whether the given IP address originates from an EU country - /// - /// IP address - /// An instance of if the IP originates from a EU country - /// - /// true if the IP address originates from an EU country, false if not - /// - bool IsEuIpAddress(string ipAddress, out Country euCountry); + LookupCountryResponse LookupCountry(IPAddress addr); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Directory/MeasureService.cs b/src/Libraries/SmartStore.Services/Directory/MeasureService.cs index 1e8c052807..1ff51e67a9 100644 --- a/src/Libraries/SmartStore.Services/Directory/MeasureService.cs +++ b/src/Libraries/SmartStore.Services/Directory/MeasureService.cs @@ -150,14 +150,14 @@ public virtual MeasureWeight GetMeasureWeightById(int measureWeightId) public virtual MeasureWeight GetMeasureWeightBySystemKeyword(string systemKeyword) { - if (String.IsNullOrEmpty(systemKeyword)) + if (!systemKeyword.HasValue()) return null; var measureWeights = GetAllMeasureWeights(); - foreach (var measureWeight in measureWeights) - if (measureWeight.SystemKeyword.ToLowerInvariant() == systemKeyword.ToLowerInvariant()) - return measureWeight; - return null; + + return measureWeights + .Where(x => x.SystemKeyword.ToLowerInvariant() == systemKeyword.ToLowerInvariant()) + .FirstOrDefault(); } public virtual IList GetAllMeasureWeights() @@ -186,8 +186,7 @@ public virtual void UpdateMeasureWeight(MeasureWeight measure) _measureWeightRepository.Update(measure); } - public virtual decimal ConvertWeight(decimal quantity, - MeasureWeight sourceMeasureWeight, MeasureWeight targetMeasureWeight, bool round = true) + public virtual decimal ConvertWeight(decimal quantity, MeasureWeight sourceMeasureWeight, MeasureWeight targetMeasureWeight, bool round = true) { decimal result = quantity; if (result != decimal.Zero && sourceMeasureWeight.Id != targetMeasureWeight.Id) @@ -195,8 +194,10 @@ public virtual decimal ConvertWeight(decimal quantity, result = ConvertToPrimaryMeasureWeight(result, sourceMeasureWeight); result = ConvertFromPrimaryMeasureWeight(result, targetMeasureWeight); } + if (round) result = Math.Round(result, 2); + return result; } @@ -214,11 +215,11 @@ public virtual decimal ConvertToPrimaryMeasureWeight(decimal quantity, MeasureWe return result; } - public virtual decimal ConvertFromPrimaryMeasureWeight(decimal quantity, - MeasureWeight targetMeasureWeight) + public virtual decimal ConvertFromPrimaryMeasureWeight(decimal quantity, MeasureWeight targetMeasureWeight) { decimal result = quantity; var baseWeightIn = GetMeasureWeightById(_measureSettings.BaseWeightId); + if (result != decimal.Zero && targetMeasureWeight.Id != baseWeightIn.Id) { decimal exchangeRatio = targetMeasureWeight.Ratio; @@ -226,6 +227,7 @@ public virtual decimal ConvertFromPrimaryMeasureWeight(decimal quantity, throw new SmartException(string.Format("Exchange ratio not set for weight [{0}]", targetMeasureWeight.Name)); result = result * exchangeRatio; } + return result; } diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountExtentions.cs index ccc62d5921..df737a6d23 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 ICollection 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/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index abb9af5b2d..660e5f23a1 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Events; -using SmartStore.Core.Plugins; using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Customers; +using SmartStore.Core.Plugins; using SmartStore.Services.Common; -using SmartStore.Services.Configuration; -using SmartStore.Collections; +using SmartStore.Services.Customers; namespace SmartStore.Services.Discounts { @@ -27,20 +25,16 @@ public partial class DiscountService : IDiscountService private readonly IRequestCache _requestCache; private readonly IStoreContext _storeContext; private readonly IGenericAttributeService _genericAttributeService; - private readonly IPluginFinder _pluginFinder; - private readonly IEventPublisher _eventPublisher; - private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; + private readonly IDictionary _discountValidityCache; - public DiscountService(IRequestCache requestCache, + public DiscountService( + IRequestCache requestCache, IRepository discountRepository, IRepository discountRequirementRepository, IRepository discountUsageHistoryRepository, IStoreContext storeContext, IGenericAttributeService genericAttributeService, - IPluginFinder pluginFinder, - IEventPublisher eventPublisher, - ISettingService settingService, IProviderManager providerManager) { _requestCache = requestCache; @@ -49,19 +43,17 @@ public DiscountService(IRequestCache requestCache, _discountUsageHistoryRepository = discountUsageHistoryRepository; _storeContext = storeContext; _genericAttributeService = genericAttributeService; - _pluginFinder = pluginFinder; - _eventPublisher = eventPublisher; - _settingService = settingService; _providerManager = providerManager; - } - - /// - /// Checks discount limitation for customer - /// - /// Discount - /// Customer - /// Value indicating whether discount can be used - protected virtual bool CheckDiscountLimitations(Discount discount, Customer customer) + _discountValidityCache = new Dictionary(); + } + + /// + /// Checks discount limitation for customer + /// + /// Discount + /// Customer + /// Value indicating whether discount can be used + protected virtual bool CheckDiscountLimitations(Discount discount, Customer customer) { Guard.NotNull(discount, nameof(discount)); @@ -98,11 +90,9 @@ protected virtual bool CheckDiscountLimitations(Discount discount, Customer cust public virtual void DeleteDiscount(Discount discount) { - if (discount == null) - throw new ArgumentNullException("discount"); - - _discountRepository.Delete(discount); + Guard.NotNull(discount, nameof(discount)); + _discountRepository.Delete(discount); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } @@ -173,21 +163,17 @@ public virtual void InsertDiscount(Discount discount) public virtual void UpdateDiscount(Discount discount) { - if (discount == null) - throw new ArgumentNullException("discount"); + Guard.NotNull(discount, nameof(discount)); _discountRepository.Update(discount); - _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } public virtual void DeleteDiscountRequirement(DiscountRequirement discountRequirement) { - if (discountRequirement == null) - throw new ArgumentNullException("discountRequirement"); - - _discountRequirementRepository.Delete(discountRequirement); + Guard.NotNull(discountRequirement, nameof(discountRequirement)); + _discountRequirementRepository.Delete(discountRequirement); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } @@ -210,31 +196,36 @@ public virtual Discount GetDiscountByCouponCode(string couponCode, bool showHidd return discount; } - public virtual bool IsDiscountValid(Discount discount, Customer customer) + public virtual bool IsDiscountValid(Discount discount, Customer customer) { - Guard.NotNull(discount, nameof(discount)); - - var couponCodeToValidate = ""; + var couponCodeToValidate = ""; if (customer != null) { couponCodeToValidate = customer.GetAttribute(SystemCustomerAttributeNames.DiscountCouponCode, _genericAttributeService); } - return IsDiscountValid(discount, customer, couponCodeToValidate); + var valid = IsDiscountValid(discount, customer, couponCodeToValidate); + return valid; } public virtual bool IsDiscountValid(Discount discount, Customer customer, string couponCodeToValidate) { Guard.NotNull(discount, nameof(discount)); + var cacheKey = new DiscountKey(discount, customer, couponCodeToValidate); + if (_discountValidityCache.TryGetValue(cacheKey, out var result)) + { + return result; + } + // Check coupon code if (discount.RequiresCouponCode) { if (discount.CouponCode.IsEmpty()) - return false; + return Cached(false); if (!discount.CouponCode.Equals(couponCodeToValidate, StringComparison.InvariantCultureIgnoreCase)) - return false; + return Cached(false); } // Check date range @@ -245,25 +236,25 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string { var startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) - return false; + return Cached(false); } if (discount.EndDateUtc.HasValue) { var endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) - return false; + return Cached(false); } if (!CheckDiscountLimitations(discount, customer)) - return false; + return Cached(false); // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) { var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); if (cart.Any(x => x.Item?.Product != null && x.Item.Product.IsGiftCard)) - return false; + return Cached(false); } // discount requirements @@ -281,12 +272,17 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string Store = store }; - // TODO: cache result... CheckRequirement is called very often if (!requirementRule.Value.CheckRequirement(request)) - return false; + return Cached(false); } - return true; + return Cached(true); + + bool Cached(bool value) + { + _discountValidityCache[cacheKey] = value; + return value; + } } public virtual DiscountUsageHistory GetDiscountUsageHistoryById(int discountUsageHistoryId) @@ -314,32 +310,34 @@ public virtual IPagedList GetAllDiscountUsageHistory(int? public virtual void InsertDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Insert(discountUsageHistory); + Guard.NotNull(discountUsageHistory, nameof(discountUsageHistory)); + _discountUsageHistoryRepository.Insert(discountUsageHistory); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } public virtual void UpdateDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Update(discountUsageHistory); + Guard.NotNull(discountUsageHistory, nameof(discountUsageHistory)); + _discountUsageHistoryRepository.Update(discountUsageHistory); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } public virtual void DeleteDiscountUsageHistory(DiscountUsageHistory discountUsageHistory) { - if (discountUsageHistory == null) - throw new ArgumentNullException("discountUsageHistory"); - - _discountUsageHistoryRepository.Delete(discountUsageHistory); + Guard.NotNull(discountUsageHistory, nameof(discountUsageHistory)); + _discountUsageHistoryRepository.Delete(discountUsageHistory); _requestCache.RemoveByPattern(DISCOUNTS_PATTERN_KEY); } + + class DiscountKey : Tuple + { + public DiscountKey(Discount discount, Customer customer, string customerCouponCode) + : base(discount, customer, customerCouponCode) + { + } + } } } diff --git a/src/Libraries/SmartStore.Services/Events/ConsumerRegistry.cs b/src/Libraries/SmartStore.Services/Events/ConsumerRegistry.cs new file mode 100644 index 0000000000..3f89138f56 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Events/ConsumerRegistry.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using SmartStore.Collections; +using SmartStore.Core.Events; + +namespace SmartStore.Services.Events +{ + public class ConsumerRegistry : IConsumerRegistry + { + private readonly Multimap _descriptorMap = new Multimap(); + + public ConsumerRegistry(IEnumerable> consumers) + { + foreach (var consumer in consumers) + { + var metadata = consumer.Metadata; + + if (!metadata.IsActive) + continue; + + var methods = FindMethods(metadata); + var messageTypes = new Dictionary(); + + foreach (var method in methods) + { + var descriptor = new ConsumerDescriptor(metadata) + { + IsAsync = method.ReturnType == typeof(Task), + FireForget = method.HasAttribute(false) + }; + + //if (descriptor.IsAsync && descriptor.FireForget) + //{ + // throw new NotSupportedException($"An asynchronous message consumer method cannot be called as fire & forget. Method: '{method}'."); + //} + + if (method.ReturnType != typeof(Task) && method.ReturnType != typeof(void)) + { + throw new NotSupportedException($"A message consumer method's return type must either be 'void' or '${typeof(Task).FullName}'. Method: '{method}'."); + } + + if (method.Name.EndsWith("Async") && !descriptor.IsAsync) + { + throw new NotSupportedException($"A synchronous message consumer method name should not end with 'Async'. Method: '{method}'."); + } + + var parameters = method.GetParameters(); + if (parameters.Length == 0) + { + throw new NotSupportedException($"A message consumer method must have at least one parameter identifying the message to consume. Method: '{method}'."); + } + + if (parameters.Any(x => x.ParameterType.IsByRef || x.IsOut || x.IsOptional)) + { + throw new NotSupportedException($"'out', 'ref' and optional parameters are not allowed in consumer methods. Method: '{method}'."); + } + + var p = parameters[0]; + var messageType = p.ParameterType; + + if (messageType.IsGenericType && messageType.GetGenericTypeDefinition() == typeof(ConsumeContext<>)) + { + messageType = messageType.GetGenericArguments()[0]; + descriptor.WithEnvelope = true; + } + + if (messageTypes.TryGetValue(messageType, out var method2)) + { + // We won't allow methods with different signatures, but same message type: there can only be one! + throw new AmbigousConsumerException(method2, method); + } + + messageTypes.Add(messageType, method); + + if (messageType.IsPublic && (messageType.IsClass || messageType.IsInterface)) + { + // The method signature is valid: add to dictionary. + descriptor.MessageParameter = p; + descriptor.Parameters = parameters.Skip(1).ToArray(); + descriptor.MessageType = messageType; + descriptor.Method = method; + + _descriptorMap.Add(messageType, descriptor); + } + else + { + // TODO: message + throw new NotSupportedException(); + } + } + } + } + + private IEnumerable FindMethods(EventConsumerMetadata metadata) + { + var methods = metadata.ContainerType.GetMethods(BindingFlags.Public | BindingFlags.Instance); + + var validNames = new HashSet(new[] { "Handle", "HandleEvent", "Consume", "HandleAsync", "HandleEventAsync", "ConsumeAsync" }); + + foreach (var method in methods) + { + if (validNames.Contains(method.Name)) + { + yield return method; + } + } + } + + public virtual IEnumerable GetConsumers(object message) + { + Guard.NotNull(message, nameof(message)); + + var type = message.GetType(); + if (_descriptorMap.ContainsKey(type)) + { + return _descriptorMap[type]; + } + + return Enumerable.Empty(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs b/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs new file mode 100644 index 0000000000..ebab9eff71 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Events/ConsumerResolver.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Autofac; +using SmartStore.Core; +using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Infrastructure.DependencyManagement; +using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Configuration; + +namespace SmartStore.Services.Events +{ + public class ConsumerResolver : IConsumerResolver + { + private readonly Work _container; + + public ConsumerResolver(Work container) + { + _container = container; + } + + public virtual IConsumer Resolve(ConsumerDescriptor descriptor) + { + if (descriptor.PluginDescriptor == null || IsActiveForStore(descriptor.PluginDescriptor)) + { + return _container.Value.ResolveKeyed(descriptor.ContainerType); + } + + return null; + } + + public virtual object ResolveParameter(ParameterInfo p, IComponentContext c = null) + { + return (c ?? _container.Value).Resolve(p.ParameterType); + } + + private bool IsActiveForStore(PluginDescriptor plugin) + { + int storeId = 0; + if (EngineContext.Current.IsFullyInitialized) + { + storeId = _container.Value.Resolve().CurrentStore.Id; + } + + if (storeId == 0) + { + return true; + } + + var settingService = _container.Value.Resolve(); + + var limitedToStoresSetting = settingService.GetSettingByKey(plugin.GetSettingKey("LimitedToStores")); + if (limitedToStoresSetting.IsEmpty()) + { + return true; + } + + var limitedToStores = limitedToStoresSetting.ToIntArray(); + if (limitedToStores.Length > 0) + { + var flag = limitedToStores.Contains(storeId); + return flag; + } + + return true; + } + } +} diff --git a/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs b/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs deleted file mode 100644 index 72382fd1c7..0000000000 --- a/src/Libraries/SmartStore.Services/Events/DefaultConsumerFactory.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using SmartStore.Core.Events; -using SmartStore.Core.Plugins; - -namespace SmartStore.Services.Events -{ - public class DefaultConsumerFactory : IConsumerFactory - { - private readonly IEnumerable, EventConsumerMetadata>> _consumers; - private readonly ICommonServices _services; - - public DefaultConsumerFactory( - IEnumerable, EventConsumerMetadata>> consumers, - ICommonServices services) - { - _consumers = consumers; - _services = services; - } - - public IEnumerable> GetConsumers(bool? resolveAsyncs = null) - { - foreach (var consumer in _consumers) - { - var isActive = consumer.Metadata.IsActive; - var isAsync = consumer.Metadata.ExecuteAsync; - var pluginDescriptor = consumer.Metadata.PluginDescriptor; - - if (isActive && (resolveAsyncs == null || (resolveAsyncs.Value == isAsync))) - { - if (pluginDescriptor == null || IsActiveForStore(pluginDescriptor, _services.StoreContext.CurrentStore.Id)) - { - yield return consumer.Value; - } - } - } - } - - - public bool HasAsyncConsumer - { - get - { - return _consumers.Any(c => c.Metadata.IsActive == true && c.Metadata.ExecuteAsync == true); - } - } - - private bool IsActiveForStore(PluginDescriptor plugin, int storeId) - { - if (storeId == 0) - { - return true; - } - - var limitedToStoresSetting = _services.Settings.GetSettingByKey(plugin.GetSettingKey("LimitedToStores")); - if (limitedToStoresSetting.IsEmpty()) - { - return true; - } - - var limitedToStores = limitedToStoresSetting.ToIntArray(); - if (limitedToStores.Length > 0) - { - var flag = limitedToStores.Contains(storeId); - return flag; - } - - return true; - } - } - -} diff --git a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs index eddb7b5a9e..e6529d9469 100644 --- a/src/Libraries/SmartStore.Services/Events/EventPublisher.cs +++ b/src/Libraries/SmartStore.Services/Events/EventPublisher.cs @@ -1,102 +1,51 @@ using System; -using System.Linq; using System.Collections.Generic; -using System.Collections.Concurrent; -using System.Threading; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Logging; -using SmartStore.Core.Async; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartStore.ComponentModel; using SmartStore.Core.Events; -using Autofac; +using SmartStore.Core.Logging; namespace SmartStore.Services.Events { - public class EventPublisher : IEventPublisher - { - private readonly ConcurrentDictionary _queue = new ConcurrentDictionary(); + public class EventPublisher : IEventPublisher + { + private readonly IConsumerRegistry _registry; + private readonly IConsumerResolver _resolver; + private readonly IConsumerInvoker _invoker; - public EventPublisher() + public EventPublisher(IConsumerRegistry registry, IConsumerResolver resolver, IConsumerInvoker invoker) { + _registry = registry; + _resolver = resolver; + _invoker = invoker; + Logger = NullLogger.Instance; } public ILogger Logger { get; set; } - public void Publish(T eventMessage) - { - if (eventMessage != null) - { - // Enable event throttling by allowing the very same event to be published only all 150 ms. - Timer timer; - if (_queue.TryGetValue(eventMessage, out timer)) - { - // do nothing. The same event was published a tick ago. - return; - } - - _queue[eventMessage] = new Timer(RemoveFromQueue, eventMessage, 150, Timeout.Infinite); - } - - var consumerFactory = EngineContext.Current.Resolve>(); - if (consumerFactory == null) - return; - - IEnumerable> consumers = null; - - // first fire/forget all async consumers - if (consumerFactory.HasAsyncConsumer) - { - AsyncRunner.Run((c, ct) => - { - // for wiring up dependencies correctly - var newFactory = c.Resolve>(); - consumers = newFactory.GetConsumers(true).ToArray(); - foreach (var consumer in consumers) - { - consumer.HandleEvent(eventMessage); - } - }).ContinueWith(t => - { - if (t.IsFaulted) - { - var ex = t.Exception; - if (ex != null) - { - ex.InnerExceptions.Each(x => Logger.Error(x)); - } - } - }); - } + public void Publish(T message) where T : class + { + var descriptors = _registry.GetConsumers(message); - // now execute all sync consumers - consumers = consumerFactory.GetConsumers(false).ToArray(); - foreach (var consumer in consumers) + if (!descriptors.Any()) { - PublishEvent(consumer, eventMessage); + return; } - } - private void PublishEvent(IConsumer x, T eventMessage) - { - try - { - x.HandleEvent(eventMessage); - } - catch (Exception ex) - { - Logger.Error(ex); - throw; - } - } + var envelopeType = typeof(ConsumeContext<>).MakeGenericType(typeof(T)); + var envelope = (ConsumeContext)FastActivator.CreateInstance(envelopeType, message); - private void RemoveFromQueue(object eventMessage) - { - Timer timer; - if (_queue.TryRemove(eventMessage, out timer)) + foreach (var descriptor in descriptors) { - timer.Dispose(); + var consumer = _resolver.Resolve(descriptor); + if (consumer != null) + { + _invoker.Invoke(descriptor, consumer, envelope); + } } } - - } + } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs index cafd22688c..54a67d93b6 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumExtensions.cs @@ -1,5 +1,4 @@ -using System; -using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; using SmartStore.Core.Html; using SmartStore.Core.Infrastructure; @@ -12,58 +11,51 @@ public static class ForumExtensions /// /// Formats the forum post text /// - /// Forum post + /// Forum post /// Formatted text - public static string FormatPostText(this ForumPost forumPost) + public static string FormatPostText(this ForumPost post) { - string text = forumPost.Text; + Guard.NotNull(post, nameof(post)); - if (String.IsNullOrEmpty(text)) - return string.Empty; - - switch (EngineContext.Current.Resolve().ForumEditor) + var text = post.Text; + if (text.IsEmpty()) { - case EditorType.SimpleTextBox: - { - text = HtmlUtils.FormatText(text, false, true, false, false, false, false); - } - break; - case EditorType.BBCodeEditor: - { - text = HtmlUtils.FormatText(text, false, true, false, true, false, false); - } - break; - default: - break; + return string.Empty; } + text = HtmlUtils.ConvertPlainTextToHtml(text.HtmlEncode()); + + if (EngineContext.Current.Resolve().ForumEditor == EditorType.BBCodeEditor) + { + text = BBCodeHelper.ToHtml(text); + } + return text; } /// /// Strips the topic subject /// - /// Forum topic + /// Forum topic /// Formatted subject - public static string StripTopicSubject(this ForumTopic forumTopic) + public static string StripTopicSubject(this ForumTopic topic) { - string subject = forumTopic.Subject; - if (String.IsNullOrEmpty(subject)) + Guard.NotNull(topic, nameof(topic)); + + var subject = topic.Subject; + if (subject.IsEmpty()) { return subject; } - int strippedTopicMaxLength = EngineContext.Current.Resolve().StrippedTopicMaxLength; - if (strippedTopicMaxLength > 0) + var strippedTopicMaxLength = EngineContext.Current.Resolve().StrippedTopicMaxLength; + if (strippedTopicMaxLength > 0 && subject.Length > strippedTopicMaxLength) { - if (subject.Length > strippedTopicMaxLength) + var index = subject.IndexOf(" ", strippedTopicMaxLength); + if (index > 0) { - int index = subject.IndexOf(" ", strippedTopicMaxLength); - if (index > 0) - { - subject = subject.Substring(0, index); - subject += "..."; - } + subject = subject.Substring(0, index); + subject += "…"; } } @@ -77,28 +69,31 @@ public static string StripTopicSubject(this ForumTopic forumTopic) /// Formatted text public static string FormatForumSignatureText(this string text) { - if (String.IsNullOrEmpty(text)) + if (text.IsEmpty()) + { return string.Empty; + } - text = SmartStore.Core.Html.HtmlUtils.FormatText(text, false, true, false, false, false, false); - return text; - } + return HtmlUtils.ConvertPlainTextToHtml(text.HtmlEncode()); + } /// /// Formats the private message text /// - /// Private message + /// Private message /// Formatted text - public static string FormatPrivateMessageText(this PrivateMessage pm) + public static string FormatPrivateMessageText(this PrivateMessage message) { - string text = pm.Text; + Guard.NotNull(message, nameof(message)); - if (String.IsNullOrEmpty(text)) + var text = message.Text; + if (text.IsEmpty()) + { return string.Empty; + } - text = SmartStore.Core.Html.HtmlUtils.FormatText(text, false, true, false, true, false, false); - - return text; + text = HtmlUtils.ConvertPlainTextToHtml(text.HtmlEncode()); + return BBCodeHelper.ToHtml(text); } /// @@ -109,8 +104,8 @@ public static string FormatPrivateMessageText(this PrivateMessage pm) /// Forum topic public static ForumTopic GetLastTopic(this Forum forum, IForumService forumService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(forumService, nameof(forumService)); return forumService.GetTopicById(forum.LastTopicId); } @@ -123,8 +118,8 @@ public static ForumTopic GetLastTopic(this Forum forum, IForumService forumServi /// Forum topic public static ForumPost GetLastPost(this Forum forum, IForumService forumService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(forumService, nameof(forumService)); return forumService.GetPostById(forum.LastPostId); } @@ -137,8 +132,8 @@ public static ForumPost GetLastPost(this Forum forum, IForumService forumService /// Customer public static Customer GetLastPostCustomer(this Forum forum, ICustomerService customerService) { - if (forum == null) - throw new ArgumentNullException("forum"); + Guard.NotNull(forum, nameof(forum)); + Guard.NotNull(customerService, nameof(customerService)); return customerService.GetCustomerById(forum.LastPostCustomerId); } @@ -146,17 +141,19 @@ public static Customer GetLastPostCustomer(this Forum forum, ICustomerService cu /// /// Get first post /// - /// Forum topic + /// Forum topic /// Forum service /// Forum post - public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService forumService) + public static ForumPost GetFirstPost(this ForumTopic topic, IForumService forumService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(forumService, nameof(forumService)); - var forumPosts = forumService.GetAllPosts(forumTopic.Id, 0, string.Empty, 0, 1); - if (forumPosts.Count > 0) - return forumPosts[0]; + var posts = forumService.GetAllPosts(topic.Id, 0, true, 0, 1); + if (posts.Count > 0) + { + return posts[0]; + } return null; } @@ -164,29 +161,29 @@ public static ForumPost GetFirstPost(this ForumTopic forumTopic, IForumService f /// /// Get last post /// - /// Forum topic + /// Forum topic /// Forum service /// Forum post - public static ForumPost GetLastPost(this ForumTopic forumTopic, IForumService forumService) + public static ForumPost GetLastPost(this ForumTopic topic, IForumService forumService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(forumService, nameof(forumService)); - return forumService.GetPostById(forumTopic.LastPostId); + return forumService.GetPostById(topic.LastPostId); } /// /// Get forum last post customer /// - /// Forum topic + /// Forum topic /// Customer service /// Customer - public static Customer GetLastPostCustomer(this ForumTopic forumTopic, ICustomerService customerService) + public static Customer GetLastPostCustomer(this ForumTopic topic, ICustomerService customerService) { - if (forumTopic == null) - throw new ArgumentNullException("forumTopic"); + Guard.NotNull(topic, nameof(topic)); + Guard.NotNull(customerService, nameof(customerService)); - return customerService.GetCustomerById(forumTopic.LastPostCustomerId); + return customerService.GetCustomerById(topic.LastPostCustomerId); } } } diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index 2fe46d227c..11b744b8ab 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -1,16 +1,15 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; +using System.Data.Entity; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Security; 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 { @@ -22,12 +21,13 @@ public partial class ForumService : IForumService private readonly IRepository _forumPostRepository; private readonly IRepository _forumPrivateMessageRepository; private readonly IRepository _forumSubscriptionRepository; + private readonly IRepository _storeMappingRepository; + private readonly IRepository _aclRepository; private readonly ForumSettings _forumSettings; private readonly IRepository _customerRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; - private readonly IRepository _storeMappingRepository; - private readonly ICommonServices _services; + private readonly ICommonServices _services; public ForumService( IRepository forumGroupRepository, @@ -36,12 +36,13 @@ public ForumService( IRepository forumPostRepository, IRepository forumPrivateMessageRepository, IRepository forumSubscriptionRepository, + IRepository storeMappingRepository, + IRepository aclRepository, ForumSettings forumSettings, IRepository customerRepository, IGenericAttributeService genericAttributeService, ICustomerService customerService, - IRepository storeMappingRepository, - ICommonServices services) + ICommonServices services) { _forumGroupRepository = forumGroupRepository; _forumRepository = forumRepository; @@ -49,647 +50,656 @@ public ForumService( _forumPostRepository = forumPostRepository; _forumPrivateMessageRepository = forumPrivateMessageRepository; _forumSubscriptionRepository = forumSubscriptionRepository; + _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; _forumSettings = forumSettings; _customerRepository = customerRepository; _genericAttributeService = genericAttributeService; _customerService = customerService; - _storeMappingRepository = storeMappingRepository; - _services = services; + _services = services; } - public DbQuerySettings QuerySettings { get; set; } + public DbQuerySettings QuerySettings { get; set; } - private void UpdateForumStats(int forumId) + protected virtual void UpdateForumStatistics(Forum forum) { - 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 == forum.Id && ft.Published && fp.Published + 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 == forum.Id && x.Published) + .Count(); + + forum.NumPosts = ( + from ft in _forumTopicRepository.Table + join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId + where ft.ForumId == forum.Id && ft.Published && fp.Published + select fp.Id).Count(); + UpdateForum(forum); } - private void UpdateForumTopicStats(int forumTopicId) + protected virtual void UpdateTopicStatistics(ForumTopic topic) { - if (forumTopicId == 0) - { - return; - } - var forumTopic = GetTopicById(forumTopicId); - if (forumTopic == null) + if (topic == 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 == topic.Id && fp.Published + 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; - UpdateTopic(forumTopic); + topic.LastPostId = lastValues?.LastPostId ?? 0; + topic.LastPostCustomerId = lastValues?.LastPostCustomerId ?? 0; + topic.LastPostTime = lastValues?.LastPostTime; + + topic.NumPosts = topic.Published + ? _forumPostRepository.Table.Where(x => x.TopicId == topic.Id && x.Published).Count() + : 0; + + UpdateTopic(topic, false); } - private void UpdateCustomerStats(int customerId) + protected virtual void UpdateCustomerStatistics(Customer customer) { - if (customerId == 0) + if (customer != null) { - return; + var numPosts = _forumPostRepository.Table + .Where(x => x.CustomerId == customer.Id && x.ForumTopic.Published && x.Published) + .Count(); + + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); } + } - var customer = _customerService.GetCustomerById(customerId); + #region Group - if (customer == null) + public virtual ForumGroup GetForumGroupById(int groupId) + { + if (groupId == 0) { - return; + return null; } - var query = from fp in _forumPostRepository.Table - where fp.CustomerId == customerId - select fp.Id; - int numPosts = query.Count(); - - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.ForumPostCount, numPosts); + return _forumGroupRepository.GetById(groupId); } - private bool IsForumModerator(Customer customer) + public virtual IList GetAllForumGroups(int storeId = 0, bool showHidden = false) { - if (customer.IsForumModerator()) + var joinApplied = false; + var query = _forumGroupRepository.Table.Expand(x => x.Forums); + + if (!QuerySettings.IgnoreMultiStore && storeId > 0) { - return true; + 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; + + joinApplied = true; } - return false; - } - public virtual void DeleteForumGroup(ForumGroup forumGroup) - { - if (forumGroup == null) + if (!showHidden && !QuerySettings.IgnoreAcl) { - throw new ArgumentNullException("forumGroup"); + var allowedCustomerRolesIds = _services.WorkContext.CurrentCustomer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); + + query = + from fg in query + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) + select fg; + + joinApplied = true; + } + + if (joinApplied) + { + query = + from fg in query + group fg by fg.Id into fgGroup + orderby fgGroup.Key + select fgGroup.FirstOrDefault(); } - _forumGroupRepository.Delete(forumGroup); + query = query.OrderBy(x => x.DisplayOrder); + + return query.ToListCached(); } - public virtual ForumGroup GetForumGroupById(int forumGroupId) + public virtual void InsertForumGroup(ForumGroup group) { - if (forumGroupId == 0) - return null; + Guard.NotNull(group, nameof(group)); - return _forumGroupRepository.GetById(forumGroupId); + _forumGroupRepository.Insert(group); } - public virtual IList GetAllForumGroups(bool showHidden = false) + public virtual void UpdateForumGroup(ForumGroup group) { - var query = _forumGroupRepository.Table; - - if (!showHidden && !QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; + Guard.NotNull(group, nameof(group)); - 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; + _forumGroupRepository.Update(group); + } - query = - from fg in query - group fg by fg.Id into fgGroup - orderby fgGroup.Key - select fgGroup.FirstOrDefault(); - } + public virtual void DeleteForumGroup(ForumGroup group) + { + if (group != null) + { + _forumGroupRepository.Delete(group); + } + } - query = query.OrderBy(m => m.DisplayOrder); + #endregion - return query.ToListCached(); - } + #region Forum - public virtual void InsertForumGroup(ForumGroup forumGroup) + public virtual Forum GetForumById(int forumId) { - if (forumGroup == null) + if (forumId == 0) { - throw new ArgumentNullException("forumGroup"); + return null; } - _forumGroupRepository.Insert(forumGroup); + var entity = _forumRepository.Table + .Expand(x => x.ForumGroup) + .FirstOrDefault(x => x.Id == forumId); + + return entity; } - public virtual void UpdateForumGroup(ForumGroup forumGroup) + public virtual void InsertForum(Forum forum) { - if (forumGroup == null) - { - throw new ArgumentNullException("forumGroup"); - } + Guard.NotNull(forum, nameof(forum)); + + _forumRepository.Insert(forum); + } + + public virtual void UpdateForum(Forum forum) + { + Guard.NotNull(forum, nameof(forum)); - _forumGroupRepository.Update(forumGroup); + _forumRepository.Update(forum); } public virtual void DeleteForum(Forum forum) - { + { if (forum == null) { - throw new ArgumentNullException("forum"); + return; } - //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; + // 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); } - //delete forum subscriptions (forum) - var queryFs2 = from fs in _forumSubscriptionRepository.Table - where fs.ForumId == forum.Id - select fs; + // 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); } - //delete forum + // Delete forum. _forumRepository.Delete(forum); } - public virtual Forum GetForumById(int forumId) - { - if (forumId == 0) - return null; + #endregion - return _forumRepository.GetById(forumId); - } + #region Topic - public virtual IList GetAllForumsByGroupId(int forumGroupId) + public virtual ForumTopic GetTopicById(int topicId) { - var query = from f in _forumRepository.Table - orderby f.DisplayOrder - where f.ForumGroupId == forumGroupId - select f; + if (topicId == 0) + { + return null; + } - var forums = query.ToListCached(); - return forums; - } + var entity = _forumTopicRepository.Table + .Expand(x => x.Forum) + .Expand(x => x.Forum.ForumGroup) + .FirstOrDefault(x => x.Id == topicId); - public virtual void InsertForum(Forum forum) + return entity; + } + + public virtual IList GetTopicsByIds(int[] topicIds) { - if (forum == null) + if (topicIds == null || topicIds.Length == 0) { - throw new ArgumentNullException("forum"); + return new List(); } - _forumRepository.Insert(forum); + var query = _forumTopicRepository.Table + .Where(x => topicIds.Contains(x.Id)); + + return query.OrderBySequence(topicIds).ToList(); } - public virtual void UpdateForum(Forum forum) + public virtual IPagedList GetAllTopics(int forumId, int pageIndex, int pageSize, bool showHidden = false) { - if (forum == null) + var customer = _services.WorkContext.CurrentCustomer; + var query = _forumTopicRepository.TableUntracked; + + if (forumId != 0) { - throw new ArgumentNullException("forum"); + query = query.Where(x => x.ForumId == forumId); } - _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); - - //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) + if (!showHidden && !customer.IsForumModerator()) { - _forumSubscriptionRepository.Delete(fs); + query = query.Where(x => x.Published || x.CustomerId == customer.Id); } - //update stats - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); - } + query = query + .OrderByDescending(x => x.TopicTypeId) + .ThenByDescending(x => x.LastPostTime) + .ThenByDescending(x => x.Id); - public virtual ForumTopic GetTopicById(int forumTopicId) - { - return GetTopicById(forumTopicId, false); + var topics = new PagedList(query, pageIndex, pageSize); + return topics; } - public virtual ForumTopic GetTopicById(int forumTopicId, bool increaseViews) + public virtual IList GetActiveTopics(int forumId, int count, bool showHidden = false) { - if (forumTopicId == 0) + var joinApplied = false; + var customer = _services.WorkContext.CurrentCustomer; + + var query = + from ft in _forumTopicRepository.Table + where (forumId == 0 || ft.ForumId == forumId) && ft.LastPostTime.HasValue + select ft; + + if (!showHidden && !customer.IsForumModerator()) { - return null; + query = query.Where(x => x.Published || x.CustomerId == customer.Id); } - var query = from ft in _forumTopicRepository.Table - where ft.Id == forumTopicId - select ft; - var forumTopic = query.SingleOrDefault(); - if (forumTopic == null) + if (!QuerySettings.IgnoreMultiStore) { - return null; + var currentStoreId = _services.StoreContext.CurrentStore.Id; + + query = + from ft in query + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + 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 ft; + + joinApplied = true; } - if (increaseViews) + if (!showHidden && !QuerySettings.IgnoreAcl) { - forumTopic.Views = ++forumTopic.Views; - UpdateTopic(forumTopic); - } + var allowedCustomerRolesIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToList(); - return forumTopic; - } + query = + from ft in query + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || allowedCustomerRolesIds.Contains(a.CustomerRoleId) + select ft; - public virtual IPagedList GetAllTopics(int forumId, int customerId, string keywords, ForumSearchType searchType, int limitDays, int pageIndex, int pageSize) - { - DateTime? limitDate = null; + joinApplied = true; + } - if (limitDays > 0) + if (joinApplied) { - limitDate = DateTime.UtcNow.AddDays(-limitDays); + query = + from ft in query + group ft by ft.Id into ftGroup + orderby ftGroup.Key + select ftGroup.FirstOrDefault(); } - 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; - - var topics = new PagedList(query2, pageIndex, pageSize); - return topics; - } + query = query.OrderByDescending(x => x.LastPostTime); - public virtual IList GetActiveTopics(int forumId, int count) - { - var query = - from ft in _forumTopicRepository.Table - where (forumId == 0 || ft.ForumId == forumId) && (ft.LastPostTime.HasValue) - select ft; - - if (!QuerySettings.IgnoreMultiStore) - { - var currentStoreId = _services.StoreContext.CurrentStore.Id; - - query = - from ft in query - join ff in _forumRepository.Table on ft.ForumId equals ff.Id - join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id - 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 ft; - - query = - from ft in query - group ft by ft.Id into ftGroup - orderby ftGroup.Key - select ftGroup.FirstOrDefault(); - } - - query = query.OrderByDescending(x => x.LastPostTime); - - var forumTopics = query.Take(count).ToList(); + var forumTopics = query.Take(() => count).ToList(); return forumTopics; } - public virtual void InsertTopic(ForumTopic forumTopic, bool sendNotifications) + public virtual void InsertTopic(ForumTopic topic, bool sendNotifications) { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } + Guard.NotNull(topic, nameof(topic)); - _forumTopicRepository.Insert(forumTopic); + _forumTopicRepository.Insert(topic); - //update stats - UpdateForumStats(forumTopic.ForumId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + + UpdateForumStatistics(forum); - //send notifications if (sendNotifications) { - var forum = forumTopic.Forum; var subscriptions = GetAllSubscriptions(0, forum.Id, 0, 0, int.MaxValue); var languageId = _services.WorkContext.WorkingLanguage.Id; foreach (var subscription in subscriptions) { - if (subscription.CustomerId == forumTopic.CustomerId) + if (subscription.CustomerId == topic.CustomerId) { continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { - _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, forumTopic, languageId); + _services.MessageFactory.SendNewForumTopicMessage(subscription.Customer, topic, languageId); } } } } - public virtual void UpdateTopic(ForumTopic forumTopic) + public virtual void UpdateTopic(ForumTopic topic, bool updateStatistics) { - if (forumTopic == null) - { - throw new ArgumentNullException("forumTopic"); - } - - _forumTopicRepository.Update(forumTopic); - } + Guard.NotNull(topic, nameof(topic)); - public virtual ForumTopic MoveTopic(int forumTopicId, int newForumId) - { - var forumTopic = GetTopicById(forumTopicId); - if (forumTopic == null) - return forumTopic; + _forumTopicRepository.Update(topic); - if (this.IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, forumTopic)) + if (updateStatistics) { - int previousForumId = forumTopic.ForumId; - var newForum = GetForumById(newForumId); - - if (newForum != null) - { - if (previousForumId != newForumId) - { - forumTopic.ForumId = newForum.Id; - UpdateTopic(forumTopic); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = topic.Customer ?? _customerService.GetCustomerById(topic.CustomerId); - //update forum stats - UpdateForumStats(previousForumId); - UpdateForumStats(newForumId); - } - } + UpdateForumStatistics(forum); + UpdateTopicStatistics(topic); + UpdateCustomerStatistics(customer); } - return forumTopic; } - public virtual void DeletePost(ForumPost forumPost) - { - if (forumPost == null) + public virtual void DeleteTopic(ForumTopic topic) + { + if (topic == null) { - throw new ArgumentNullException("forumPost"); + return; } - int forumTopicId = forumPost.TopicId; - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumTopicId); - int forumId = forumTopic.ForumId; + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = topic.Customer ?? _customerService.GetCustomerById(topic.CustomerId); + + _forumTopicRepository.Delete(topic); - //delete topic if it was the first post - bool deleteTopic = false; - var firstPost = forumTopic.GetFirstPost(this); - if (firstPost != null && firstPost.Id == forumPost.Id) + // Delete forum subscriptions. + var subscriptions = _forumSubscriptionRepository.Table.Where(x => x.TopicId == topic.Id).ToList(); + foreach (var subscription in subscriptions) { - deleteTopic = true; + _forumSubscriptionRepository.Delete(subscription); } - //delete forum post - _forumPostRepository.Delete(forumPost); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); + } - //delete topic - if (deleteTopic) + public virtual ForumTopic MoveTopic(int topicId, int newForumId) + { + var topic = GetTopicById(topicId); + if (topic != null && IsCustomerAllowedToMoveTopic(_services.WorkContext.CurrentCustomer, topic)) { - DeleteTopic(forumTopic); - } + var previousForumId = topic.ForumId; + var newForum = GetForumById(newForumId); - //update stats - if (!deleteTopic) - { - UpdateForumTopicStats(forumTopicId); + if (newForum != null && previousForumId != newForumId) + { + topic.ForumId = newForum.Id; + UpdateTopic(topic, false); + + UpdateForumStatistics(GetForumById(previousForumId)); + UpdateForumStatistics(newForum); + } } - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + return topic; } - public virtual ForumPost GetPostById(int forumPostId) + public virtual int CalculateTopicPageIndex(int topicId, int pageSize, int postId) { - if (forumPostId == 0) + if (pageSize > 0 && postId != 0) { - return null; - } + var query = GetPostQuery(topicId, 0, true, true); + var postIds = query.Select(x => x.Id).ToList(); - var query = from fp in _forumPostRepository.Table - where fp.Id == forumPostId - select fp; - var forumPost = query.SingleOrDefault(); + for (var i = 0; i < postIds.Count; ++i) + { + if (postIds[i] == postId) + { + return i / pageSize; + } + } + } - return forumPost; + return 0; } - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, int pageIndex, int pageSize) - { - return GetAllPosts(forumTopicId, customerId, keywords, true, pageIndex, pageSize); - } + #endregion + + #region Post - public virtual IPagedList GetAllPosts(int forumTopicId, int customerId, string keywords, bool ascSort, int pageIndex, int pageSize) + protected virtual IQueryable GetPostQuery( + int topicId, + int customerId, + bool ascSort, + bool showHidden = false, + bool untracked = false) { - var query = _forumPostRepository.Table; - if (forumTopicId > 0) + var customer = _services.WorkContext.CurrentCustomer; + var query = untracked ? _forumPostRepository.TableUntracked : _forumPostRepository.Table; + + if (topicId > 0) { - query = query.Where(fp => forumTopicId == fp.TopicId); + query = query.Where(x => topicId == x.TopicId); } if (customerId > 0) { - query = query.Where(fp => customerId == fp.CustomerId); + query = query.Where(x => customerId == x.CustomerId); } - if (!String.IsNullOrEmpty(keywords)) + if (!showHidden && !customer.IsForumModerator()) { - query = query.Where(fp => fp.Text.Contains(keywords)); + query = query.Where(x => x.Published || x.CustomerId == customer.Id); } + if (ascSort) { - query = query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); + query = query.OrderBy(x => x.CreatedOnUtc).ThenBy(x => x.Id); } else { - query = query.OrderByDescending(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id); + query = query.OrderByDescending(x => x.CreatedOnUtc).ThenBy(x => x.Id); } - var forumPosts = new PagedList(query, pageIndex, pageSize); + return query; + } - return forumPosts; + public virtual ForumPost GetPostById(int postId) + { + if (postId == 0) + { + return null; + } + + var forumPost = _forumPostRepository.Table + .Expand(x => x.ForumTopic) + .Expand(x => x.ForumTopic.Forum) + .Expand(x => x.ForumTopic.Forum.ForumGroup) + .FirstOrDefault(x => x.Id == postId); + + return forumPost; } - public virtual void InsertPost(ForumPost forumPost, bool sendNotifications) + public virtual IList GetPostsByIds(int[] postIds) { - if (forumPost == null) + if (postIds == null || postIds.Length == 0) { - throw new ArgumentNullException("forumPost"); + return new List(); } - _forumPostRepository.Insert(forumPost); + var query = _forumPostRepository.TableUntracked.Expand(x => x.ForumTopic).Expand(x => x.Customer) + .Where(x => postIds.Contains(x.Id)); - //update stats - int customerId = forumPost.CustomerId; - var forumTopic = this.GetTopicById(forumPost.TopicId); - int forumId = forumTopic.ForumId; - UpdateForumTopicStats(forumPost.TopicId); - UpdateForumStats(forumId); - UpdateCustomerStats(customerId); + return query.OrderBySequence(postIds).ToList(); + } + + public virtual IPagedList GetAllPosts( + int topicId, + int customerId, + bool ascSort, + int pageIndex, + int pageSize, + bool showHidden = false) + { + var query = GetPostQuery(topicId, customerId, ascSort, showHidden); + var forumPosts = new PagedList(query, pageIndex, pageSize); + return forumPosts; + } + + public virtual void InsertPost(ForumPost post, bool sendNotifications) + { + Guard.NotNull(post, nameof(post)); + + _forumPostRepository.Insert(post); + + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); + + UpdateTopicStatistics(topic); + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); - //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, topic.Id, 0, int.MaxValue); + var friendlyTopicPageIndex = CalculateTopicPageIndex( + post.TopicId, + _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 20, + post.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) + if (subscription.CustomerId == post.CustomerId) { continue; } - if (!String.IsNullOrEmpty(subscription.Customer.Email)) + if (subscription.Customer.Email.HasValue()) { - _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, forumPost, friendlyTopicPageIndex, languageId); + _services.MessageFactory.SendNewForumPostMessage(subscription.Customer, post, friendlyTopicPageIndex, languageId); } } } } - public virtual void UpdatePost(ForumPost forumPost) + public virtual void UpdatePost(ForumPost post, bool updateStatistics) { - //validation - if (forumPost == null) + Guard.NotNull(post, nameof(post)); + + _forumPostRepository.Update(post); + + if (updateStatistics) { - throw new ArgumentNullException("forumPost"); - } + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); - _forumPostRepository.Update(forumPost); + UpdateForumStatistics(forum); + UpdateTopicStatistics(topic); + UpdateCustomerStatistics(customer); + } } - public virtual void DeletePrivateMessage(PrivateMessage privateMessage) + public virtual void DeletePost(ForumPost post) { - if (privateMessage == null) + if (post == null) { - throw new ArgumentNullException("privateMessage"); + return; } - _forumPrivateMessageRepository.Delete(privateMessage); + var topic = post.ForumTopic ?? GetTopicById(post.TopicId); + var forum = topic.Forum ?? GetForumById(topic.ForumId); + var customer = post.Customer ?? _customerService.GetCustomerById(post.CustomerId); + + // Delete topic if it was the first post. + var firstPost = topic.GetFirstPost(this); + var deleteTopic = firstPost != null && firstPost.Id == post.Id; + + // Delete forum post. + _forumPostRepository.Delete(post); + + // Delete topic. + if (deleteTopic) + { + DeleteTopic(topic); + } + else + { + UpdateTopicStatistics(topic); + } + + UpdateForumStatistics(forum); + UpdateCustomerStatistics(customer); } - public virtual PrivateMessage GetPrivateMessageById(int privateMessageId) + #endregion + + #region Private message + + public virtual PrivateMessage GetPrivateMessageById(int messageId) { - if (privateMessageId == 0) + if (messageId == 0) { 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 == messageId); return privateMessage; } @@ -700,100 +710,96 @@ 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) + public virtual void InsertPrivateMessage(PrivateMessage message) { - if (privateMessage == null) - { - throw new ArgumentNullException("privateMessage"); - } + Guard.NotNull(message, nameof(message)); - _forumPrivateMessageRepository.Insert(privateMessage); + _forumPrivateMessageRepository.Insert(message); - var customerTo = _customerService.GetCustomerById(privateMessage.ToCustomerId); + var customerTo = _customerService.GetCustomerById(message.ToCustomerId); if (customerTo == null) { throw new SmartException("Recipient could not be loaded"); } - //UI notification - _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, privateMessage.StoreId); + _genericAttributeService.SaveAttribute(customerTo, SystemCustomerAttributeNames.NotifiedAboutNewPrivateMessages, false, message.StoreId); - //Email notification if (_forumSettings.NotifyAboutPrivateMessages) { - _services.MessageFactory.SendPrivateMessageNotification(customerTo, privateMessage, _services.WorkContext.WorkingLanguage.Id); + _services.MessageFactory.SendPrivateMessageNotification(customerTo, message, _services.WorkContext.WorkingLanguage.Id); } } - public virtual void UpdatePrivateMessage(PrivateMessage privateMessage) + public virtual void UpdatePrivateMessage(PrivateMessage message) { - if (privateMessage == null) - throw new ArgumentNullException("privateMessage"); + Guard.NotNull(message, nameof(message)); - if (privateMessage.IsDeletedByAuthor && privateMessage.IsDeletedByRecipient) + if (message.IsDeletedByAuthor && message.IsDeletedByRecipient) { - _forumPrivateMessageRepository.Delete(privateMessage); + _forumPrivateMessageRepository.Delete(message); } else { - _forumPrivateMessageRepository.Update(privateMessage); + _forumPrivateMessageRepository.Update(message); } } - public virtual void DeleteSubscription(ForumSubscription forumSubscription) + public virtual void DeletePrivateMessage(PrivateMessage message) { - if (forumSubscription == null) + if (message != null) { - throw new ArgumentNullException("forumSubscription"); - } - - _forumSubscriptionRepository.Delete(forumSubscription); + _forumPrivateMessageRepository.Delete(message); + } } - public virtual ForumSubscription GetSubscriptionById(int forumSubscriptionId) + #endregion + + #region Subscription + + public virtual ForumSubscription GetSubscriptionById(int subscriptionId) { - if (forumSubscriptionId == 0) + if (subscriptionId == 0) { 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 == subscriptionId); return forumSubscription; } @@ -819,34 +825,35 @@ where fsQuery.Contains(fs.SubscriptionGuid) return forumSubscriptions; } - public virtual void InsertSubscription(ForumSubscription forumSubscription) + public virtual void InsertSubscription(ForumSubscription subscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } + Guard.NotNull(subscription, nameof(subscription)); - _forumSubscriptionRepository.Insert(forumSubscription); + _forumSubscriptionRepository.Insert(subscription); } - public virtual void UpdateSubscription(ForumSubscription forumSubscription) + public virtual void UpdateSubscription(ForumSubscription subscription) { - if (forumSubscription == null) - { - throw new ArgumentNullException("forumSubscription"); - } - - _forumSubscriptionRepository.Update(forumSubscription); + Guard.NotNull(subscription, nameof(subscription)); + + _forumSubscriptionRepository.Update(subscription); } - public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) + public virtual void DeleteSubscription(ForumSubscription subscription) { - if (forum == null) + if (subscription != null) { - return false; + _forumSubscriptionRepository.Delete(subscription); } + } + + #endregion - if (customer == null) + #region Customer + + public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum forum) + { + if (forum == null || customer == null) { return false; } @@ -856,7 +863,7 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru return false; } - if (IsForumModerator(customer)) + if (customer.IsForumModerator()) { return true; } @@ -866,30 +873,18 @@ public virtual bool IsCustomerAllowedToCreateTopic(Customer customer, Forum foru public virtual bool IsCustomerAllowedToEditTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer != null && topic != null) { - return false; - } - - if (IsForumModerator(customer)) - { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToEditPosts) - { - bool ownTopic = customer.Id == topic.CustomerId; - return ownTopic; + if (_forumSettings.AllowCustomersToEditPosts && topic.Published) + { + var ownTopic = customer.Id == topic.CustomerId; + return ownTopic; + } } return false; @@ -897,22 +892,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) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (customer != null && customer.IsForumModerator()) { return true; } @@ -922,30 +902,18 @@ public virtual bool IsCustomerAllowedToMoveTopic(Customer customer, ForumTopic t public virtual bool IsCustomerAllowedToDeleteTopic(Customer customer, ForumTopic topic) { - if (topic == null) - { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) + if (topic != null && customer != null) { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToDeletePosts) - { - bool ownTopic = customer.Id == topic.CustomerId; - return ownTopic; + if (_forumSettings.AllowCustomersToDeletePosts && topic.Published) + { + var ownTopic = customer.Id == topic.CustomerId; + return ownTopic; + } } return false; @@ -953,17 +921,12 @@ 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; } - if (customer.IsGuest() && !_forumSettings.AllowGuestsToCreatePosts) + if (!_forumSettings.AllowGuestsToCreatePosts && customer.IsGuest()) { return false; } @@ -973,30 +936,18 @@ public virtual bool IsCustomerAllowedToCreatePost(Customer customer, ForumTopic public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost post) { - if (post == null) - { - return false; - } - - if (customer == null) + if (post != null && customer != null) { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) - { - return true; - } + if (customer.IsForumModerator()) + { + return true; + } - if (_forumSettings.AllowCustomersToEditPosts) - { - bool ownPost = customer.Id == post.CustomerId; - return ownPost; + if (_forumSettings.AllowCustomersToEditPosts && post.Published) + { + var ownPost = customer.Id == post.CustomerId; + return ownPost; + } } return false; @@ -1004,63 +955,26 @@ public virtual bool IsCustomerAllowedToEditPost(Customer customer, ForumPost pos public virtual bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post) { - if (post == null) + if (post != null && customer != null) { - return false; - } - - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) - { - return false; - } - - if (IsForumModerator(customer)) - { - return true; - } - - if (_forumSettings.AllowCustomersToDeletePosts) - { - bool ownPost = customer.Id == post.CustomerId; - return ownPost; - } - - return false; - } - - public virtual bool IsCustomerAllowedToSetTopicPriority(Customer customer) - { - if (customer == null) - { - return false; - } + if (customer.IsForumModerator()) + { + return true; + } - if (customer.IsGuest()) - { - return false; + if (_forumSettings.AllowCustomersToDeletePosts && post.Published) + { + var ownPost = customer.Id == post.CustomerId; + return ownPost; + } } - if (IsForumModerator(customer)) - { - return true; - } - return false; } public virtual bool IsCustomerAllowedToSubscribe(Customer customer) { - if (customer == null) - { - return false; - } - - if (customer.IsGuest()) + if (customer == null || customer.IsGuest()) { return false; } @@ -1068,23 +982,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..22706359df 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -10,42 +10,44 @@ namespace SmartStore.Services.Forums /// public partial interface IForumService { - /// - /// Deletes a forum group - /// - /// Forum group - void DeleteForumGroup(ForumGroup forumGroup); + #region Group /// /// Gets a forum group /// - /// The forum group identifier + /// The forum group identifier /// Forum group - ForumGroup GetForumGroupById(int forumGroupId); + ForumGroup GetForumGroupById(int groupId); /// /// Gets all forum groups /// + /// Store identifier + /// Whether to load hidden records /// Forum groups - IList GetAllForumGroups(bool showHidden = false); + IList GetAllForumGroups(int storeId = 0, bool showHidden = false); /// - /// Inserts a forum group + /// Deletes a forum group /// - /// Forum group - void InsertForumGroup(ForumGroup forumGroup); + /// Forum group + void DeleteForumGroup(ForumGroup group); /// - /// Updates the forum group + /// Inserts a forum group /// - /// Forum group - void UpdateForumGroup(ForumGroup forumGroup); + /// Forum group + void InsertForumGroup(ForumGroup group); /// - /// Deletes a forum + /// Updates the forum group /// - /// Forum - void DeleteForum(Forum forum); + /// Forum group + void UpdateForumGroup(ForumGroup group); + + #endregion + + #region Forum /// /// Gets a forum @@ -55,11 +57,10 @@ public partial interface IForumService Forum GetForumById(int forumId); /// - /// Gets forums by group identifier + /// Deletes a forum /// - /// The forum group identifier - /// Forums - IList GetAllForumsByGroupId(int forumGroupId); + /// Forum + void DeleteForum(Forum forum); /// /// Inserts a forum @@ -73,132 +74,140 @@ 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 /// - /// The forum topic identifier + /// The forum topic identifier /// Forum Topic - ForumTopic GetTopicById(int forumTopicId); + ForumTopic GetTopicById(int topicId); /// - /// 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 + /// Whether to load hidden records /// 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, bool showHidden = false); /// /// Gets active forum topics /// /// The forum identifier /// Count of forum topics to return + /// Whether to load hidden records /// Forum Topics - IList GetActiveTopics(int forumId, int topicCount); + IList GetActiveTopics(int forumId, int topicCount, bool showHidden = false); + + /// + /// Deletes a forum topic + /// + /// Forum topic + void DeleteTopic(ForumTopic topic); /// /// Inserts a forum topic /// - /// Forum topic + /// Forum topic /// A value indicating whether to send notifications to subscribed customers - void InsertTopic(ForumTopic forumTopic, bool sendNotifications); + void InsertTopic(ForumTopic topic, bool sendNotifications); /// /// Updates the forum topic /// - /// Forum topic - void UpdateTopic(ForumTopic forumTopic); + /// Forum topic + /// A value indicating whether to update counter. + void UpdateTopic(ForumTopic topic, bool updateStatistics); /// /// Moves the forum topic /// - /// The forum topic identifier + /// The forum topic identifier /// New forum identifier /// Moved forum topic - ForumTopic MoveTopic(int forumTopicId, int newForumId); + ForumTopic MoveTopic(int topicId, 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 topicId, int pageSize, int postId); + + #endregion + + #region Post /// /// Gets a forum post /// - /// The forum post identifier + /// The forum post identifier /// Forum Post - ForumPost GetPostById(int forumPostId); + ForumPost GetPostById(int postId); /// - /// 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 forum topic identifier /// The customer identifier - /// Keywords /// Sort order /// Page index /// Page size + /// Whether to load hidden records /// Forum Posts - IPagedList GetAllPosts(int forumTopicId, int customerId, - string keywords, bool ascSort, int pageIndex, int pageSize); + IPagedList GetAllPosts(int topicId, int customerId, bool ascSort, int pageIndex, int pageSize, bool showHidden = false); + + /// + /// Deletes a forum post + /// + /// Forum post + void DeletePost(ForumPost post); /// /// Inserts a forum post /// - /// The forum post + /// The forum post /// A value indicating whether to send notifications to subscribed customers - void InsertPost(ForumPost forumPost, bool sendNotifications); + void InsertPost(ForumPost post, bool sendNotifications); /// /// Updates the forum post /// - /// Forum post - void UpdatePost(ForumPost forumPost); + /// Forum post + /// A value indicating whether to update counter. + void UpdatePost(ForumPost post, bool updateStatistics); - /// - /// Deletes a private message - /// - /// Private message - void DeletePrivateMessage(PrivateMessage privateMessage); + #endregion + + #region Private message /// /// Gets a private message /// - /// The private message identifier + /// The private message identifier /// Private message - PrivateMessage GetPrivateMessageById(int privateMessageId); + PrivateMessage GetPrivateMessageById(int messageId); /// /// Gets private messages @@ -209,31 +218,40 @@ 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); /// - /// Inserts a private message + /// Deletes a private message /// - /// Private message - void InsertPrivateMessage(PrivateMessage privateMessage); + /// Private message + void DeletePrivateMessage(PrivateMessage message); /// - /// Updates the private message + /// Inserts a private message /// - /// Private message - void UpdatePrivateMessage(PrivateMessage privateMessage); + /// Private message + void InsertPrivateMessage(PrivateMessage message); /// - /// Deletes a forum subscription + /// Updates the private message /// - /// Forum subscription - void DeleteSubscription(ForumSubscription forumSubscription); + /// Private message + void UpdatePrivateMessage(PrivateMessage message); + + #endregion + + #region Subscription /// /// Gets a forum subscription @@ -254,6 +272,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 +290,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 /// @@ -322,13 +350,6 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// True if allowed, otherwise false bool IsCustomerAllowedToDeletePost(Customer customer, ForumPost post); - /// - /// Check whether customer is allowed to set topic priority - /// - /// Customer - /// True if allowed, otherwise false - bool IsCustomerAllowedToSetTopicPriority(Customer customer); - /// /// Check whether customer is allowed to watch topics /// @@ -336,13 +357,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..ba90b19cc8 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Hooks/UpdateCustomerFullNameHook.cs @@ -0,0 +1,63 @@ +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; + +namespace SmartStore.Services.Hooks +{ + [Important] + public class UpdateCustomerFullNameHook : DbSaveHook + { + private static readonly HashSet _candidateProps = new HashSet(new string[] + { + nameof(Customer.Title), + nameof(Customer.Salutation), + nameof(Customer.FirstName), + nameof(Customer.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/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index a65704ea59..979ef91ca5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -42,11 +42,44 @@ public partial interface ILocalizedEntityService : IScopedService /// Localized properties IList GetLocalizedProperties(int entityId, string localeKeyGroup); - /// - /// Inserts a localized property - /// - /// Localized property - void InsertLocalizedProperty(LocalizedProperty localizedProperty); + /// + /// Prefetches a collection of localized properties for a range of entities in one go + /// and caches them for the duration of the current request. + /// + /// Locale key group (scope) + /// + /// The entity ids to prefetch translations for. Can be null, + /// in which case all translations for the requested scope are loaded. + /// + /// Whether represents a range of ids (perf). + /// Whether is already sorted (perf). + /// Localized property collection + /// + /// Be careful not to load large amounts of data at once (e.g. for "Product" scope with large range). + /// + void PrefetchLocalizedProperties(string localeKeyGroup, int languageId, int[] entityIds, bool isRange = false, bool isSorted = false); + + /// + /// Gets a collection of localized properties for a range of entities in one go. + /// + /// Locale key group (scope) + /// + /// The entity ids to load translations for. Can be null, + /// in which case all translations for the requested scope are loaded. + /// + /// Whether represents a range of ids (perf). + /// Whether is already sorted (perf). + /// Localized property collection + /// + /// Be careful not to load large amounts of data at once (e.g. for "Product" scope with large range). + /// + LocalizedPropertyCollection GetLocalizedPropertyCollection(string localeKeyGroup, int[] entityIds, bool isRange = false, bool isSorted = false); + + /// + /// Inserts a localized property + /// + /// Localized property + void InsertLocalizedProperty(LocalizedProperty localizedProperty); /// /// Updates the localized property diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs index b6c97aeb47..eef621f93f 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtensions.cs @@ -24,17 +24,15 @@ 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, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().WorkingLanguage, detectEmptyHtml: detectEmptyHtml); } @@ -49,7 +47,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, @@ -57,14 +55,12 @@ public static string GetLocalized(this T entity, bool detectEmptyHtml = false) where T : BaseEntity, ILocalizedEntity { - Guard.NotNull(entity, nameof(entity)); - - return GetLocalized( + return GetLocalizedEx( entity, - typeof(T).Name, - entity.Id, - keySelector, - languageId, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().GetLanguageById(languageId), returnDefaultValue, ensureTwoPublishedLanguages, detectEmptyHtml); @@ -74,7 +70,68 @@ public static string GetLocalized(this T entity, /// Get localized property of an entity /// /// Entity type - /// Property type + /// Entity + /// Key selector + /// Language identifier + /// 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, + string localeKey, + TProp fallback, + Language language, + bool returnDefaultValue = true, + bool ensureTwoPublishedLanguages = true, + bool detectEmptyHtml = false) + where T : BaseEntity, ILocalizedEntity + { + return GetLocalizedEx( + entity, + entity.GetEntityName(), + localeKey, + x => fallback, + language, + 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 + { + return GetLocalizedEx( + entity, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + language, + returnDefaultValue, + ensureTwoPublishedLanguages, + detectEmptyHtml); + } + + /// + /// Get localized property of an entity + /// + /// Entity type + /// Property type /// Entity /// Key selector /// Language identifier @@ -82,43 +139,70 @@ 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, - Expression> keySelector, + public static LocalizedValue GetLocalized(this T entity, + Expression> keySelector, int languageId, bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true, bool detectEmptyHtml = false) where T : BaseEntity, ILocalizedEntity { - Guard.NotNull(entity, nameof(entity)); - - return GetLocalized( - entity, - typeof(T).Name, - entity.Id, - keySelector, - languageId, + return GetLocalizedEx( + entity, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + 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 + { + return GetLocalizedEx( + entity, + entity.GetEntityName(), + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + 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); + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().WorkingLanguage); } /// @@ -128,90 +212,115 @@ 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", + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + EngineContext.Current.Resolve().GetLanguageById(languageId)); + } - return GetLocalized( + /// + /// Get localized property of an instance + /// + /// Node + /// Key selector + /// /// Language + /// Localized property + public static LocalizedValue GetLocalized(this ICategoryNode node, Expression> keySelector, Language language) + { + return GetLocalizedEx( node, "Category", - node.Id, - keySelector, - languageId); + LocaleKeyFromExpression(keySelector.Body), + x => keySelector.Compile().Invoke(x), + language); } - internal static TPropType GetLocalized(T entity, + internal static LocalizedValue GetLocalizedEx(T entity, string localeKeyGroup, - int entityId, - Expression> keySelector, - int languageId, + string localeKey, + Func fallback, + Language requestLanguage, bool returnDefaultValue = true, bool ensureTwoPublishedLanguages = true, bool detectEmptyHtml = false) where T : ILocalizedEntity { - TPropType result = default(TPropType); - string resultStr = string.Empty; + if (entity == null) + throw new ArgumentNullException(nameof(entity)); + + TProp result = default; + var str = string.Empty; - if (entityId > 0) + var languageService = EngineContext.Current.Resolve(); + + Language currentLanguage = null; + if (requestLanguage != null) { - var member = keySelector.Body as MemberExpression; - if (member == null) + // Ensure that we have at least two published languages + var loadLocalizedValue = true; + if (ensureTwoPublishedLanguages) { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a method, not a property.", - keySelector)); + var totalPublishedLanguages = languageService.GetLanguagesCount(false); + loadLocalizedValue = totalPublishedLanguages >= 2; } - var propInfo = member.Member as PropertyInfo; - if (propInfo == null) + // Localized value + if (loadLocalizedValue) { - throw new ArgumentException(string.Format( - "Expression '{0}' refers to a field, not a property.", - keySelector)); - } - - // Load localized value - string localeKey = propInfo.Name; + var leService = EngineContext.Current.Resolve(); + str = leService.GetLocalizedValue(requestLanguage.Id, entity.Id, localeKeyGroup, localeKey); - if (languageId > 0) - { - // Ensure that we have at least two published languages - bool loadLocalizedValue = true; - if (ensureTwoPublishedLanguages) + if (detectEmptyHtml && str.HasValue() && str.RemoveHtml().IsEmpty()) { - var lService = EngineContext.Current.Resolve(); - var totalPublishedLanguages = lService.GetLanguagesCount(false); - loadLocalizedValue = totalPublishedLanguages >= 2; + str = string.Empty; } - // Localized value - if (loadLocalizedValue) + if (str.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 = (TProp)str.Convert(typeof(TProp), CultureInfo.InvariantCulture); } } } - + // Set default value if required - if (returnDefaultValue && resultStr.IsEmpty()) + if (returnDefaultValue && str.IsEmpty()) { - var localizer = keySelector.Compile(); - result = localizer(entity); + currentLanguage = languageService.GetLanguageById(languageService.GetDefaultLanguageId()); + result = fallback(entity); } - return result; + if (requestLanguage == null) + { + requestLanguage = EngineContext.Current.Resolve().WorkingLanguage; + } + + if (currentLanguage == null) + { + currentLanguage = requestLanguage; + } + + return new LocalizedValue(result, requestLanguage, currentLanguage); + } + + private static string LocaleKeyFromExpression(Expression expression) + { + if (!(expression is MemberExpression member)) + { + throw new ArgumentException($"Expression '{expression}' refers to a method, not to a property."); + } + + var propInfo = member.Member as PropertyInfo; + if (propInfo == null) + { + throw new ArgumentException($"Expression '{expression}' refers to a field, not to a property."); + } + + return propInfo.Name; } @@ -226,9 +335,10 @@ internal static TPropType GetLocalized(T entity, public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, IWorkContext workContext) where T : struct { - Guard.NotNull(workContext, nameof(workContext)); + if (workContext == null) + throw new ArgumentNullException(nameof(workContext)); - return GetLocalizedEnum(enumValue, localizationService, workContext.WorkingLanguage.Id); + return GetLocalizedEnum(enumValue, localizationService, workContext.WorkingLanguage.Id); } /// @@ -242,7 +352,8 @@ public static string GetLocalizedEnum(this T enumValue, ILocalizationService public static string GetLocalizedEnum(this T enumValue, ILocalizationService localizationService, int languageId = 0) where T : struct { - Guard.NotNull(localizationService, nameof(localizationService)); + if (localizationService == null) + throw new ArgumentNullException(nameof(localizationService)); if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type"); @@ -266,13 +377,11 @@ public static string GetLocalizedEnum(this T enumValue, ILocalizationService /// /// Plugin /// Resource name - public static void DeletePluginLocaleResource(this BasePlugin plugin, - string resourceName) + public static void DeletePluginLocaleResource(this BasePlugin plugin, string resourceName) { var localizationService = EngineContext.Current.Resolve(); var languageService = EngineContext.Current.Resolve(); - DeletePluginLocaleResource(plugin, localizationService, - languageService, resourceName); + DeletePluginLocaleResource(plugin, localizationService, languageService, resourceName); } /// @@ -282,17 +391,20 @@ public static void DeletePluginLocaleResource(this BasePlugin plugin, /// Localization service /// Language service /// Resource name - public static void DeletePluginLocaleResource(this BasePlugin plugin, - ILocalizationService localizationService, ILanguageService languageService, + public static void DeletePluginLocaleResource(this BasePlugin plugin, + ILocalizationService localizationService, + ILanguageService languageService, string resourceName) { - //actually plugin instance is not required + // actually plugin instance is not required if (plugin == null) - throw new ArgumentNullException("plugin"); + throw new ArgumentNullException(nameof(plugin)); + if (localizationService == null) - throw new ArgumentNullException("localizationService"); + throw new ArgumentNullException(nameof(localizationService)); + if (languageService == null) - throw new ArgumentNullException("languageService"); + throw new ArgumentNullException(nameof(languageService)); foreach (var lang in languageService.GetAllLanguages(true)) { @@ -326,10 +438,12 @@ public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, /// Resource name /// Resource value public static void AddOrUpdatePluginLocaleResource(this BasePlugin plugin, - ILocalizationService localizationService, ILanguageService languageService, - string resourceName, string resourceValue) + ILocalizationService localizationService, + ILanguageService languageService, + string resourceName, + string resourceValue) { - //actually plugin instance is not required + // Actually plugin instance is not required if (plugin == null) throw new ArgumentNullException("plugin"); if (localizationService == null) @@ -387,7 +501,7 @@ public static string GetLocalizedValue(this T plugin, ILocalizationService lo where T : IPlugin { if (plugin == null) - throw new ArgumentNullException("plugin"); + throw new ArgumentNullException(nameof(plugin)); if (plugin.PluginDescriptor == null) throw new ArgumentNullException("PluginDescriptor cannot be loaded"); @@ -407,13 +521,13 @@ public static string GetLocalizedValue(this T plugin, ILocalizationService lo public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocalizationService localizationService, string propertyName, int languageId = 0, bool returnDefaultValue = true) { if (localizationService == null) - throw new ArgumentNullException("localizationService"); + throw new ArgumentNullException(nameof(localizationService)); if (descriptor == null) - throw new ArgumentNullException("descriptor"); + throw new ArgumentNullException(nameof(descriptor)); if (propertyName == null) - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(propertyName)); string systemName = descriptor.SystemName; string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); @@ -464,16 +578,16 @@ public static void SaveLocalizedValue(this PluginDescriptor descriptor, ILocaliz string propertyName, string value) { if (localizationService == null) - throw new ArgumentNullException("localizationService"); + throw new ArgumentNullException(nameof(localizationService)); if (languageId == 0) - throw new ArgumentOutOfRangeException("languageId", "Language ID should not be 0"); + throw new ArgumentOutOfRangeException(nameof(languageId), "Language ID should not be 0"); if (descriptor == null) - throw new ArgumentNullException("descriptor"); + throw new ArgumentNullException(nameof(descriptor)); if (propertyName == null) - throw new ArgumentNullException("name"); + throw new ArgumentNullException(nameof(propertyName)); string systemName = descriptor.SystemName; string resourceName = string.Format("Plugins.{0}.{1}", propertyName, systemName); @@ -523,7 +637,7 @@ public static void ImportResourcesFromXml(this ILocalizationService service, bool updateTouchedResources = false) { if (language == null) - throw new ArgumentNullException("language"); + throw new ArgumentNullException(nameof(language)); if (String.IsNullOrEmpty(xml)) return; diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index bac41c3830..d85eafb668 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)) { @@ -319,7 +319,7 @@ public virtual void ImportPluginResourcesFromXml( bool updateTouchedResources = true, IList filterLanguages = null) { - var directory = new DirectoryInfo(Path.Combine(pluginDescriptor.OriginalAssemblyFile.Directory.FullName, "Localization")); + var directory = new DirectoryInfo(Path.Combine(pluginDescriptor.Assembly.OriginalFile.Directory.FullName, "Localization")); if (!directory.Exists) return; @@ -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..8d3c9bd41c 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -3,9 +3,11 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Threading; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Localization @@ -19,18 +21,51 @@ 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; private readonly ICacheManager _cacheManager; + private readonly PerformanceSettings _performanceSettings; + + private readonly IDictionary _prefetchedCollections; + private static int _lastCacheSegmentSize = -1; public LocalizedEntityService( ICacheManager cacheManager, - IRepository localizedPropertyRepository) + IRepository localizedPropertyRepository, + PerformanceSettings performanceSettings) { _cacheManager = cacheManager; _localizedPropertyRepository = localizedPropertyRepository; + _performanceSettings = performanceSettings; + + _prefetchedCollections = new Dictionary(StringComparer.OrdinalIgnoreCase); + + ValidateCacheState(); + } + + private void ValidateCacheState() + { + // Ensure that after a segment size change the cache segments are invalidated. + var size = _performanceSettings.CacheSegmentSize; + var changed = _lastCacheSegmentSize == -1; + + if (size <= 0) + { + _performanceSettings.CacheSegmentSize = size = 1; + } + + if (_lastCacheSegmentSize > 0 && _lastCacheSegmentSize != size) + { + OnClearCache(); + changed = true; + } + + if (changed) + { + Interlocked.Exchange(ref _lastCacheSegmentSize, size); + } } protected override void OnClearCache() @@ -38,12 +73,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,25 +104,38 @@ 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) { + if (_prefetchedCollections.TryGetValue(localeKeyGroup, out var collection)) + { + var cachedItem = collection.Find(languageId, entityId, localeKey); + if (cachedItem != null) + { + return cachedItem.LocaleValue; + } + } + if (IsInScope) { return GetLocalizedValueUncached(languageId, entityId, localeKeyGroup, localeKey); @@ -96,7 +144,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)) { @@ -127,16 +175,85 @@ public virtual IList GetLocalizedProperties(int entityId, str if (localeKeyGroup.IsEmpty()) return new List(); - var query = from lp in _localizedPropertyRepository.Table - orderby lp.Id - where lp.EntityId == entityId && - lp.LocaleKeyGroup == localeKeyGroup - select lp; + var query = from x in _localizedPropertyRepository.Table + orderby x.Id + where x.EntityId == entityId && x.LocaleKeyGroup == localeKeyGroup + select x; var props = query.ToList(); return props; } + public virtual void PrefetchLocalizedProperties(string localeKeyGroup, int languageId, int[] entityIds, bool isRange = false, bool isSorted = false) + { + if (languageId == 0) + return; + + var collection = GetLocalizedPropertyCollectionInternal(localeKeyGroup, languageId, entityIds, isRange, isSorted); + + if (_prefetchedCollections.TryGetValue(localeKeyGroup, out var existing)) + { + collection.MergeWith(existing); + } + else + { + _prefetchedCollections[localeKeyGroup] = collection; + } + } + + public virtual LocalizedPropertyCollection GetLocalizedPropertyCollection(string localeKeyGroup, int[] entityIds, bool isRange = false, bool isSorted = false) + { + return GetLocalizedPropertyCollectionInternal(localeKeyGroup, 0, entityIds, isRange, isSorted); + } + + public virtual LocalizedPropertyCollection GetLocalizedPropertyCollectionInternal(string localeKeyGroup, int languageId, int[] entityIds, bool isRange = false, bool isSorted = false) + { + Guard.NotEmpty(localeKeyGroup, nameof(localeKeyGroup)); + + using (new DbContextScope(proxyCreation: false, lazyLoading: false)) + { + var query = from x in _localizedPropertyRepository.TableUntracked + where x.LocaleKeyGroup == localeKeyGroup + select x; + + var requestedSet = entityIds; + + if (entityIds != null && entityIds.Length > 0) + { + if (isRange) + { + if (!isSorted) + { + Array.Sort(entityIds); + } + + var min = entityIds[0]; + var max = entityIds[entityIds.Length - 1]; + + if (entityIds.Length == 2 && max > min + 1) + { + // Only min & max were passed, create the range sequence. + requestedSet = Enumerable.Range(min, max - min + 1).ToArray(); + } + + query = query.Where(x => x.EntityId >= min && x.EntityId <= max); + } + else + { + requestedSet = entityIds; + query = query.Where(x => entityIds.Contains(x.EntityId)); + } + } + + if (languageId > 0) + { + query = query.Where(x => x.LanguageId == languageId); + } + + return new LocalizedPropertyCollection(localeKeyGroup, requestedSet, query.ToList()); + } + } + protected virtual LocalizedProperty GetLocalizedProperty(int languageId, int entityId, string localeKeyGroup, string localeKey) { var query = from lp in _localizedPropertyRepository.Table @@ -158,12 +275,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 +287,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); @@ -237,7 +342,7 @@ public virtual void SaveLocalizedValue( throw new ArgumentException($"Expression '{keySelector}' refers to a field, not a property."); } - var keyGroup = typeof(T).Name; + var keyGroup = entity.GetEntityName(); var key = propInfo.Name; var valueStr = localeValue.Convert(); var prop = GetLocalizedProperty(languageId, entity.Id, keyGroup, key); @@ -282,23 +387,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(_performanceSettings.CacheSegmentSize, 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..625f53f986 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedValue.cs @@ -0,0 +1,161 @@ +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 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 void ChangeValue(T value) + { + _value = value; + } + + 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/CachedImageResult.cs b/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs index 613192513a..8761739ab8 100644 --- a/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs +++ b/src/Libraries/SmartStore.Services/Media/CachedImageResult.cs @@ -13,6 +13,7 @@ namespace SmartStore.Services.Media public class CachedImageResult { private bool? _exists; + private string _mimeType; public CachedImageResult(IFile file) { @@ -59,11 +60,19 @@ public long FileSize /// The file extension (without 'dot') /// public string Extension { get; set; } - - /// - /// The path relative to the cache root folder - /// - public string Path { get; set; } + + /// + /// The filemime type + /// + public string MimeType + { + get => _mimeType ?? (_mimeType = MimeTypes.MapNameToMimeType(FileName)); + } + + /// + /// The path relative to the cache root folder + /// + public string Path { get; set; } /// /// The last modified date or null if the file does not exist diff --git a/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs b/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs index 01f8d58440..1c5e8dc893 100644 --- a/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs +++ b/src/Libraries/SmartStore.Services/Media/DefaultImageProcessor.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Logging; using ImageProcessor.Configuration; using SmartStore.Core.Events; +using SmartStore.Utilities; namespace SmartStore.Services.Media { @@ -48,6 +49,7 @@ public ProcessImageResult ProcessImage(ProcessImageQuery query) ValidateQuery(query); var watch = new Stopwatch(); + byte[] inBuffer = null; try { @@ -58,28 +60,36 @@ public ProcessImageResult ProcessImage(ProcessImageQuery query) var source = query.Source; // Load source - if (source is byte[]) + if (source is byte[] b) { - processor.Load((byte[])source); + inBuffer = b; } - else if (source is Stream) + else if (source is Stream s) { - processor.Load((Stream)source); + inBuffer = s.ToByteArray(); } - else if (source is Image) + else if (source is Image img) { - processor.Load((Image)source); + processor.Load(img); } - else if (source is string) + else if (source is string str) { - // TODO: (mc) map virtual pathes - processor.Load((string)source); + var path = NormalizePath(str); + using (var fs = File.OpenRead(path)) + { + inBuffer = fs.ToByteArray(); + } } else { throw new ProcessImageException("Invalid source type '{0}' in query.".FormatInvariant(query.Source.GetType().FullName), query); } + if (inBuffer != null) + { + processor.Load(inBuffer); + } + // Pre-process event _eventPublisher.Publish(new ImageProcessingEvent(query, processor)); @@ -87,25 +97,60 @@ public ProcessImageResult ProcessImage(ProcessImageQuery query) { Query = query, SourceWidth = processor.Image.Width, - SourceHeight = processor.Image.Height + SourceHeight = processor.Image.Height, + SourceMimeType = processor.CurrentImageFormat.MimeType }; // Core processing - ProcessImageCore(query, processor); + ProcessImageCore(query, processor, out var fxApplied); // Create & prepare result var outStream = new MemoryStream(); processor.Save(outStream); + var fmt = processor.CurrentImageFormat; + result.FileExtension = fmt.DefaultExtension == "jpeg" ? "jpg" : fmt.DefaultExtension; + result.MimeType = fmt.MimeType; + + result.HasAppliedVisualEffects = fxApplied; result.Width = processor.Image.Width; result.Height = processor.Image.Height; - result.FileExtension = processor.CurrentImageFormat.DefaultExtension; - result.MimeType = processor.CurrentImageFormat.MimeType; - result.OutputStream = outStream; + + if (inBuffer != null) + { + // Check whether it is more beneficial to return the source instead of the result. + // Prefer result only if its size is smaller than the source size. + // Result size may be larger if a high-compressed image has been uploaded. + // During image processing the source compression algorithm gets lost and the image may be larger in size + // after encoding with default encoders. + var compare = + // only when image was not altered visually... + !fxApplied + // ...size has not changed + && result.Width == result.SourceWidth + && result.Height == result.SourceHeight + // ...and format has not changed + && result.MimeType == result.SourceMimeType; + + if (compare && inBuffer.LongLength <= outStream.GetBuffer().LongLength) + { + // Source is smaller. Throw away result and get back to source. + outStream.Dispose(); + result.OutputStream = new MemoryStream(inBuffer, 0, inBuffer.Length, true, true); + } + } + + // Set output stream + if (result.OutputStream == null) + { + result.OutputStream = outStream; + } // Post-process event _eventPublisher.Publish(new ImageProcessedEvent(query, processor, result)); + result.OutputStream.Position = 0; + result.ProcessTimeMs = watch.ElapsedMilliseconds; return result; @@ -119,9 +164,9 @@ public ProcessImageResult ProcessImage(ProcessImageQuery query) } finally { - if (query.DisposeSource && query.Source is IDisposable) + if (query.DisposeSource && query.Source is IDisposable source) { - ((IDisposable)query.Source).Dispose(); + source.Dispose(); } watch.Stop(); @@ -134,8 +179,14 @@ public ProcessImageResult ProcessImage(ProcessImageQuery query) /// /// Query /// Processor instance - protected virtual void ProcessImageCore(ProcessImageQuery query, ImageFactory processor) + /// + /// Should be true if any effect has been applied that potentially changes the image visually (like background color, contrast, sharpness etc.). + /// Resize and compression quality does NOT count as FX. + /// + protected virtual void ProcessImageCore(ProcessImageQuery query, ImageFactory processor, out bool fxApplied) { + fxApplied = false; + // Resize var size = query.MaxWidth != null || query.MaxHeight != null ? new Size(query.MaxWidth ?? 0, query.MaxHeight ?? 0) @@ -143,8 +194,17 @@ protected virtual void ProcessImageCore(ProcessImageQuery query, ImageFactory pr if (!size.IsEmpty) { - var scaleMode = ConvertScaleMode(query.ScaleMode); - processor.Resize(new ResizeLayer(size, resizeMode: scaleMode, upscale: false)); + processor.Resize(new ResizeLayer( + size, + resizeMode: ConvertScaleMode(query.ScaleMode), + anchorPosition: ConvertAnchorPosition(query.AnchorPosition), + upscale: false)); + } + + if (query.BackgroundColor.HasValue()) + { + processor.BackgroundColor(ColorTranslator.FromHtml(query.BackgroundColor)); + fxApplied = true; } // Format @@ -183,6 +243,21 @@ protected virtual void ProcessImageCore(ProcessImageQuery query, ImageFactory pr } } + private string NormalizePath(string path) + { + if (path.IsWebUrl()) + { + throw new NotSupportedException($"Remote images cannot be processed: Path: {path}"); + } + + if (!PathHelper.IsAbsolutePhysicalPath(path)) + { + path = CommonHelper.MapPath(path); + } + + return path; + } + private void ValidateQuery(ProcessImageQuery query) { if (query.Source == null) @@ -214,5 +289,30 @@ private ResizeMode ConvertScaleMode(string mode) return ResizeMode.Max; } } + + private AnchorPosition ConvertAnchorPosition(string anchor) + { + switch (anchor.EmptyNull().ToLower()) + { + case "top": + return AnchorPosition.Top; + case "bottom": + return AnchorPosition.Bottom; + case "left": + return AnchorPosition.Left; + case "right": + return AnchorPosition.Right; + case "top-left": + return AnchorPosition.TopLeft; + case "top-right": + return AnchorPosition.TopRight; + case "bottom-left": + return AnchorPosition.BottomLeft; + case "bottom-right": + return AnchorPosition.BottomRight; + default: + return AnchorPosition.Center; + } + } } } 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..68c98f8926 100644 --- a/src/Libraries/SmartStore.Services/Media/ImageCache.cs +++ b/src/Libraries/SmartStore.Services/Media/ImageCache.cs @@ -19,21 +19,12 @@ public class ImageCache : IImageCache private readonly MediaSettings _mediaSettings; private readonly string _thumbsRootDir; - private readonly IStoreContext _storeContext; - private readonly HttpContextBase _httpContext; private readonly IMediaFileSystem _fileSystem; private readonly IImageProcessor _imageProcessor; - public ImageCache( - MediaSettings mediaSettings, - IStoreContext storeContext, - HttpContextBase httpContext, - IMediaFileSystem fileSystem, - IImageProcessor imageProcessor) + public ImageCache(MediaSettings mediaSettings, IMediaFileSystem fileSystem, IImageProcessor imageProcessor) { _mediaSettings = mediaSettings; - _storeContext = storeContext; - _httpContext = httpContext; _fileSystem = fileSystem; _imageProcessor = imageProcessor; @@ -61,25 +52,19 @@ public void Put(CachedImageResult cachedImage, byte[] buffer) } } - public Task PutAsync(CachedImageResult cachedImage, byte[] buffer) + public async Task PutAsync(CachedImageResult cachedImage, byte[] buffer) { if (PreparePut(cachedImage, buffer)) { var path = BuildPath(cachedImage.Path); // save file - var t = _fileSystem.WriteAllBytesAsync(path, buffer); - t.ContinueWith(x => - { - // Refresh info - cachedImage.Exists = true; - cachedImage.File = _fileSystem.GetFile(path); - }); - - return t; - } + await _fileSystem.WriteAllBytesAsync(path, buffer); - return Task.FromResult(false); + // Refresh info + cachedImage.Exists = true; + cachedImage.File = _fileSystem.GetFile(path); + } } private bool PreparePut(CachedImageResult cachedImage, byte[] buffer) @@ -121,10 +106,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 +150,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 +159,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,9 +173,21 @@ 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++) + for (int i = 0; i < 10; i++) { try { @@ -248,6 +273,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..9e421212eb 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Entity; using System.Drawing; @@ -25,7 +24,7 @@ namespace SmartStore.Services.Media { - [Serializable] + [Serializable] public class PictureInfo { public int Id { get; set; } @@ -53,7 +52,6 @@ public partial class PictureService : IPictureService private readonly IImageProcessor _imageProcessor; private readonly IImageCache _imageCache; private readonly Provider _storageProvider; - private readonly IStoreContext _storeContext; private readonly HttpContextBase _httpContext; private readonly ICacheManager _cacheManager; @@ -65,8 +63,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/"; } @@ -79,7 +76,7 @@ public PictureService( IImageProcessor imageProcessor, IImageCache imageCache, IProviderManager providerManager, - IStoreContext storeContext, + IStoreContext storeContext, HttpContextBase httpContext, ICacheManager cacheManager) { @@ -90,7 +87,6 @@ public PictureService( _mediaSettings = mediaSettings; _imageProcessor = imageProcessor; _imageCache = imageCache; - _storeContext = storeContext; _httpContext = httpContext; _cacheManager = cacheManager; @@ -113,7 +109,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 { @@ -144,9 +140,6 @@ protected virtual string GetFallbackImageFileName(FallbackPictureType defaultPic case FallbackPictureType.Entity: defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; - case FallbackPictureType.Avatar: - defaultImageFileName = _settingService.GetSettingByKey("Media.Customer.DefaultAvatarImageName", "default-avatar.jpg"); - break; default: defaultImageFileName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.png"); break; @@ -161,8 +154,7 @@ protected virtual string GetFallbackImageFileName(FallbackPictureType defaultPic public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType) { - var size = Size.Empty; - return ValidatePicture(pictureBinary, mimeType, out size); + return ValidatePicture(pictureBinary, mimeType, out _); } public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out Size size) @@ -172,7 +164,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) @@ -215,14 +207,16 @@ public virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType, out public virtual byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable pictures, out int equalPictureId) { equalPictureId = 0; + + var myStream = new MemoryStream(pictureBinary); + try { foreach (var picture in pictures) { - var otherPictureBinary = LoadPictureBinary(picture); + myStream.Seek(0, SeekOrigin.Begin); - using (var myStream = new MemoryStream(pictureBinary)) - using (var otherStream = new MemoryStream(otherPictureBinary)) + using (var otherStream = OpenPictureStream(picture)) { if (myStream.ContentsEqual(otherStream)) { @@ -238,6 +232,10 @@ public virtual byte[] FindEqualPicture(byte[] pictureBinary, IEnumerable LoadPictureBinaryAsync(Picture picture) return _storageProvider.Value.LoadAsync(picture.ToMedia()); } - public virtual Size GetPictureSize(byte[] pictureBinary) - { - if (pictureBinary == null || pictureBinary.Length == 0) - { - return Size.Empty; - } - - return GetPictureSize(new MemoryStream(pictureBinary), false); - } - - protected virtual Size GetPictureSize(Stream input, bool leaveOpen = true) + public virtual Size GetPictureSize(byte[] pictureBinary, string mimeType = null) { - 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) @@ -324,7 +274,7 @@ public IDictionary GetPictureInfos(IEnumerable pictureIds { PictureId = id, CacheKey = cacheKey, - Info = _cacheManager.Contains(cacheKey) ? _cacheManager.Get(cacheKey) : (PictureInfo)null + Info = _cacheManager.Contains(cacheKey) ? _cacheManager.Get(cacheKey, independent: true) : (PictureInfo)null }).ToList(); var result = new Dictionary(allRequestedInfos.Count); @@ -364,7 +314,7 @@ public PictureInfo GetPictureInfo(int? pictureId) var info = _cacheManager.Get(cacheKey, () => { return CreatePictureInfo(GetPictureById(pictureId.GetValueOrDefault())); - }); + }, independent: true); return info; } @@ -378,7 +328,7 @@ public PictureInfo GetPictureInfo(Picture picture) var info = _cacheManager.Get(cacheKey, () => { return CreatePictureInfo(picture); - }); + }, independent: true); return info; } @@ -522,7 +472,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; @@ -562,7 +512,7 @@ public int ClearUrlCache() public virtual string GetPictureSeName(string name) { - return SeoHelper.GetSeName(name, true, false); + return SeoHelper.GetSeName(name, true, false, false); } public virtual Picture SetSeoFilename(int pictureId, string seoFilename) @@ -609,7 +559,7 @@ orderby pp.DisplayOrder select p; if (recordsToReturn > 0) - query = query.Take(recordsToReturn); + query = query.Take(() => recordsToReturn); var pics = query.ToList(); return pics; @@ -639,7 +589,7 @@ group pp by pp.ProductId into g ProductId = g.Key, Pictures = g.OrderBy(x => x.DisplayOrder) .Take(take) - .Select(x => new { PictureId = x.PictureId, ProductId = x.ProductId }) + .Select(x => new { x.PictureId, x.ProductId }) }; var groupingResult = query.ToDictionary(x => x.ProductId, x => x.Pictures); diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs index 1cb2038497..d16095ff8c 100644 --- a/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageException.cs @@ -21,23 +21,25 @@ public ProcessImageException(string message, ProcessImageQuery query) } public ProcessImageException(ProcessImageQuery query, Exception innerException) - : base(CreateMessage(query), innerException) + : base(CreateMessage(query, innerException), innerException) { Query = query; } - private static string CreateMessage(ProcessImageQuery query) + private static string CreateMessage(ProcessImageQuery query, Exception innerException) { var fileName = query?.FileName; - if (fileName.HasValue()) - { - return "Error while processing image '{0}'.".FormatCurrent(fileName); - } - else + var msg = fileName.HasValue() + ? "Error while processing image '{0}'".FormatCurrent(fileName) + : "Error while processing image"; + + if (innerException != null) { - return "Error while processing image."; + msg += " (" + innerException.Message + ")"; } + + return msg; } public ProcessImageQuery Query { get; private set; } diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageQuery.cs index 253a6fc033..9b3c5998e2 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. /// @@ -94,13 +117,30 @@ public int? Quality set { Set("q", value); } } - // TODO: (mc) make Enum + /// + /// max (default) | boxpad | crop | min | pad | stretch + /// public string ScaleMode { get { return Get("m"); } set { Set("m", value); } } + /// + /// center (default) | top | bottom | left | right | top-left | top-right | bottom-left | bottom-right + /// + public string AnchorPosition + { + get { return Get("pos"); } + set { Set("pos", value); } + } + + public string BackgroundColor + { + get { return Get("bg"); } + set { Set("bg", value); } + } + /// /// Gets or sets the output file format either as a string ("png", "jpg", and "gif"), /// or as a format object instance. @@ -110,6 +150,7 @@ public string ScaleMode public bool IsValidationMode { get; set; } + public bool Notify { get; set; } private T Get(string name) { @@ -125,9 +166,24 @@ 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; + } + + if (base.Count == 1 && Quality == 100) + { + // If "q" is the only flag and its value is 100, we don't need to process + return false; + } + + return true; } public string CreateHash() diff --git a/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs b/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs index e520fcfefd..9ff224d143 100644 --- a/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs +++ b/src/Libraries/SmartStore.Services/Media/ProcessImageResult.cs @@ -11,12 +11,19 @@ public class ProcessImageResult : DisposableObject public int? SourceWidth { get; set; } public int? SourceHeight { get; set; } + public string SourceMimeType { get; set; } public string FileExtension { get; set; } public string MimeType { get; set; } public int Width { get; set; } public int Height { get; set; } + /// + /// Is true if any effect has been applied that changed the image visually (like background color, contrast, sharpness etc.). + /// Resize and compression quality does NOT count as FX. + /// + public bool HasAppliedVisualEffects { get; set; } + public long ProcessTimeMs { get; set; } protected override void OnDispose(bool disposing) diff --git a/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs index b67c04fbe5..4810d8f0bc 100644 --- a/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs +++ b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs @@ -1,6 +1,7 @@ using System; +using System.Data.Entity; using System.Linq; -using System.Linq.Expressions; +using System.Threading.Tasks; using SmartStore.Core.Data; using SmartStore.Core.Domain.Media; using SmartStore.Services.Tasks; @@ -9,61 +10,71 @@ namespace SmartStore.Services.Media { /// /// Represents a task for deleting transient media from the database - /// (pictures and downloads which have been uploaded but never assigned to an entity) + /// (pictures and downloads which have been uploaded but never assigned to an entity). /// - public partial class TransientMediaClearTask : ITask + public partial class TransientMediaClearTask : AsyncTask { private readonly IPictureService _pictureService; + private readonly IDownloadService _downloadService; private readonly IRepository _pictureRepository; private readonly IRepository _downloadRepository; public TransientMediaClearTask( IPictureService pictureService, - IRepository pictureRepository, + IDownloadService downloadService, + IRepository pictureRepository, IRepository downloadRepository) { - this._pictureService = pictureService; - this._pictureRepository = pictureRepository; - this._downloadRepository = downloadRepository; + _pictureService = pictureService; + _downloadService = downloadService; + _pictureRepository = pictureRepository; + _downloadRepository = downloadRepository; } - public void Execute(TaskExecutionContext ctx) + public override async Task ExecuteAsync(TaskExecutionContext ctx) { - // delete all media records which are in transient state since at least 3 hours + // Delete all media records which are in transient state since at least 3 hours. var olderThan = DateTime.UtcNow.AddHours(-3); - - // delete Downloads - _downloadRepository.DeleteAll(x => x.IsTransient && x.UpdatedOnUtc < olderThan); - - // delete Pictures - var autoCommit = _pictureRepository.AutoCommitEnabled; - _pictureRepository.AutoCommitEnabled = false; + var pictureAutoCommit = _pictureRepository.AutoCommitEnabled; + var downloadAutoCommit = _downloadRepository.AutoCommitEnabled; + + _pictureRepository.AutoCommitEnabled = false; + _downloadRepository.AutoCommitEnabled = false; try { using (var scope = new DbContextScope(autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { - var pictures = _pictureRepository.Table.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToList(); + var pictures = await _pictureRepository.Table.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToListAsync(); foreach (var picture in pictures) { _pictureService.DeletePicture(picture); } - _pictureRepository.Context.SaveChanges(); + await _pictureRepository.Context.SaveChangesAsync(); - if (DataSettings.Current.IsSqlServer) - { - try - { - _pictureRepository.Context.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); - } - catch { } - } - } - } - finally + var downloads = await _downloadRepository.Table.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToListAsync(); + foreach (var download in downloads) + { + _downloadService.DeleteDownload(download); + } + + await _downloadRepository.Context.SaveChangesAsync(); + + if (DataSettings.Current.IsSqlServer && (pictures.Any() || downloads.Any())) + { + try + { + _pictureRepository.Context.ExecuteSqlCommand("DBCC SHRINKDATABASE(0)", true); + } + catch { } + } + } + } + finally { - _pictureRepository.AutoCommitEnabled = autoCommit; + _pictureRepository.AutoCommitEnabled = pictureAutoCommit; + _downloadRepository.AutoCommitEnabled = downloadAutoCommit; } } } diff --git a/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs b/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs index ac9ffb04ab..b1fffacea2 100644 --- a/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs +++ b/src/Libraries/SmartStore.Services/Messages/CreateAttachmentsConsumer.cs @@ -13,7 +13,7 @@ namespace SmartStore.Services.Messages { - public class CreateAttachmentsConsumer : IConsumer + public class CreateAttachmentsConsumer : IConsumer { private readonly PdfSettings _pdfSettings; private readonly HttpRequestBase _httpRequest; 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/IQueuedEmailService.cs b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs index 9bbdc2d02d..82a6de9bac 100644 --- a/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/IQueuedEmailService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading.Tasks; using SmartStore.Core; using SmartStore.Core.Domain.Messages; @@ -52,11 +53,11 @@ public partial interface IQueuedEmailService IPagedList SearchEmails(SearchEmailsQuery query); /// - /// Sends a queued email + /// Sends a queued email asynchronously. /// /// Queued email /// Whether the operation succeeded - bool SendEmail(QueuedEmail queuedEmail); + Task SendEmailAsync(QueuedEmail queuedEmail); /// /// Gets a queued email attachment by identifier diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 3b81f017f6..93ba2c8044 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -4,19 +4,16 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Messages; using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.DataExchange.Import.Events; namespace SmartStore.Services.Messages.Importer { public class NewsLetterSubscriptionImporter : IEntityImporter { - private readonly ICommonServices _services; private readonly IRepository _subscriptionRepository; - public NewsLetterSubscriptionImporter( - ICommonServices services, - IRepository subscriptionRepository) + public NewsLetterSubscriptionImporter(IRepository subscriptionRepository) { - _services = services; _subscriptionRepository = subscriptionRepository; } @@ -39,9 +36,9 @@ public static string[] DefaultKeyFields public void Execute(ImportExecuteContext context) { var utcNow = DateTime.UtcNow; - var currentStoreId = _services.StoreContext.CurrentStore.Id; + var currentStoreId = context.Services.StoreContext.CurrentStore.Id; - using (var scope = new DbContextScope(ctx: _services.DbContext, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, hooksEnabled: false, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { var segmenter = context.DataSegmenter; @@ -147,7 +144,9 @@ public void Execute(ImportExecuteContext context) } // for _subscriptionRepository.Context.SaveChanges(); - } // while + + context.Services.EventPublisher.Publish(new ImportBatchExecutedEvent(context, batch)); + } // while } } } diff --git a/src/Libraries/SmartStore.Services/Messages/MessageContext.cs b/src/Libraries/SmartStore.Services/Messages/MessageContext.cs index 9d3014b713..056ab70126 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageContext.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageContext.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; @@ -10,11 +9,11 @@ namespace SmartStore.Services.Messages { - /// - /// A context object which contains all required and optional information - /// for the creation of message templates. - /// - public class MessageContext + /// + /// A context object which contains all required and optional information + /// for the creation of message templates. + /// + public class MessageContext { private IFormatProvider _formatProvider; @@ -88,18 +87,6 @@ public IFormatProvider FormatProvider } } - private IFormatProvider GetFormatProvider(MessageContext messageContext) - { - var culture = messageContext.Language.LanguageCulture; - - if (LocalizationHelper.IsValidCultureCode(culture)) - { - return CultureInfo.GetCultureInfo(culture); - } - - return CultureInfo.CurrentCulture; - } - public static MessageContext Create(string messageTemplateName, int languageId, int? storeId = null, Customer customer = null) { return new MessageContext diff --git a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs index d04fb30d29..a70a2b480e 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageFactory.cs @@ -22,6 +22,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Templating; +using SmartStore.Utilities; namespace SmartStore.Services.Messages { @@ -87,6 +88,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); @@ -181,7 +188,7 @@ private EmailAddress RenderEmailAddress(string email, MessageContext ctx, bool r { parsed = RenderTemplate(email, ctx, required); - if (required || parsed != null) + if (required || parsed.HasValue()) { return parsed.Convert(); } @@ -216,7 +223,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 +364,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 +384,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 +434,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 +448,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,8 +627,8 @@ private object GetModelFromExpression(string expression, IDictionary 0) { // Fetch a random one - var skip = new Random().Next(count - 1); - result = query.OrderBy(x => x.Id).Skip(skip).FirstOrDefault(); + var skip = CommonHelper.GenerateRandomInteger(0, 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..b88fda1d9a 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; } @@ -392,9 +451,10 @@ protected virtual object CreateModelPart(ReturnRequest part, MessageContext mess { "Reason", part.ReasonForReturn.NullEmpty() }, { "Status", part.ReturnRequestStatus.GetLocalizedEnum(_services.Localization, messageContext.Language.Id) }, { "RequestedAction", part.RequestedAction.NullEmpty() }, - { "CustomerComments", HtmlUtils.FormatText(part.CustomerComments, true, false, false, false, false, false).NullEmpty() }, - { "StaffNotes", HtmlUtils.FormatText(part.StaffNotes, true, false, false, false, false, false).NullEmpty() }, + { "CustomerComments", HtmlUtils.StripTags(part.CustomerComments).NullEmpty() }, + { "StaffNotes", HtmlUtils.StripTags(part.StaffNotes).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..8cc14ed9d5 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); @@ -175,20 +258,35 @@ public virtual void AddModelPart(object part, MessageContext messageContext, str case ForumPost x: modelPart = CreateModelPart(x, messageContext); break; + case ForumPostVote x: + modelPart = CreateModelPart(x, messageContext); + break; case Forum x: modelPart = CreateModelPart(x, messageContext); break; 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); @@ -371,7 +469,7 @@ protected virtual object CreateModelPart(Store part, MessageContext messageConte var host = messageContext.BaseUri.ToString(); var logoInfo = _services.PictureService.GetPictureInfo(messageContext.Store.LogoPictureId); - // Issue: https://github.com/smartstoreag/SmartStoreNET/issues/1321 + // Issue: https://github.com/smartstore/SmartStoreNET/issues/1321 var m = new Dictionary { @@ -389,9 +487,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 +545,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 +559,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 +576,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); @@ -496,12 +599,13 @@ protected virtual object CreateModelPart(Customer part, MessageContext messageCo var email = part.FindEmail(); var pwdRecoveryToken = part.GetAttribute(SystemCustomerAttributeNames.PasswordRecoveryToken).NullEmpty(); var accountActivationToken = part.GetAttribute(SystemCustomerAttributeNames.AccountActivationToken).NullEmpty(); + var customerVatStatus = (VatNumberStatus)part.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - int rewardPointsBalance = part.GetRewardPointsBalance(); + int rewardPointsBalance = part.GetRewardPointsBalance(); decimal rewardPointsAmountBase = _services.Resolve().ConvertRewardPointsToAmount(rewardPointsBalance); decimal rewardPointsAmount = _services.Resolve().ConvertFromPrimaryStoreCurrency(rewardPointsAmountBase, _services.WorkContext.WorkingCurrency); - var m = new Dictionary + var m = new Dictionary { ["Id"] = part.Id, ["CustomerGuid"] = part.CustomerGuid, @@ -515,18 +619,18 @@ 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(), + ["VatNumberStatus"] = customerVatStatus.GetLocalizedEnum(_services.Localization, messageContext.Language.Id).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 +639,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 +660,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() } }; @@ -564,17 +668,16 @@ protected virtual object CreateModelPart(GiftCard part, MessageContext messageCo var message = (string)null; if (part.Message.HasValue()) { - message = HtmlUtils.FormatText(part.Message, true, false, false, false, false, false); + message = HtmlUtils.StripTags(part.Message); } 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 +719,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 @@ -643,7 +746,7 @@ protected virtual object CreateModelPart(ProductReview part, MessageContext mess var m = new Dictionary { { "Title", part.Title.NullEmpty() }, - { "Text", HtmlUtils.FormatText(part.ReviewText, true, false, false, false, false, false).NullEmpty() }, + { "Text", HtmlUtils.StripTags(part.ReviewText).NullEmpty() }, { "Rating", part.Rating } }; @@ -652,6 +755,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)); @@ -699,7 +840,7 @@ protected virtual object CreateModelPart(NewsComment part, MessageContext messag { { "NewsTitle", part.NewsItem.Title.NullEmpty() }, { "Title", part.CommentTitle.NullEmpty() }, - { "Text", HtmlUtils.FormatText(part.CommentText, true, false, false, false, false, false).NullEmpty() }, + { "Text", HtmlUtils.StripTags(part.CommentText).NullEmpty() }, { "NewsUrl", BuildRouteUrl("NewsItem", new { SeName = part.NewsItem.GetSeName(messageContext.Language.Id) }, messageContext) } }; @@ -750,15 +891,35 @@ protected virtual object CreateModelPart(ForumPost part, MessageContext messageC return m; } - protected virtual object CreateModelPart(Forum part, MessageContext messageContext) + protected virtual object CreateModelPart(ForumPostVote part, MessageContext messageContext) + { + Guard.NotNull(messageContext, nameof(messageContext)); + Guard.NotNull(part, nameof(part)); + + var m = new Dictionary + { + { "ForumPostId", part.ForumPostId }, + { "Vote", part.Vote }, + { "TopicId", part.ForumPost.TopicId }, + { "TopicSubject", part.ForumPost.ForumTopic.Subject.NullEmpty() }, + }; + + ApplyCustomerContentPart(m, part, messageContext); + + PublishModelPartCreatedEvent(part, m); + + return m; + } + + protected virtual object CreateModelPart(Forum part, MessageContext messageContext) { Guard.NotNull(messageContext, nameof(messageContext)); Guard.NotNull(part, nameof(part)); 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 +947,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 +1004,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/Messages/QueuedEmailService.cs b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs index 8b7ebd258c..00ee3e397e 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedEmailService.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net.Mail; +using System.Threading.Tasks; using System.Web; using SmartStore.Core; using SmartStore.Core.Data; @@ -184,7 +185,7 @@ public virtual IPagedList SearchEmails(SearchEmailsQuery query) return queuedEmails; } - public virtual bool SendEmail(QueuedEmail queuedEmail) + public virtual async Task SendEmailAsync(QueuedEmail queuedEmail) { var result = false; @@ -193,7 +194,7 @@ public virtual bool SendEmail(QueuedEmail queuedEmail) var smtpContext = new SmtpContext(queuedEmail.EmailAccount); var msg = ConvertEmail(queuedEmail); - _emailSender.SendEmail(smtpContext, msg); + await _emailSender.SendEmailAsync(smtpContext, msg); queuedEmail.SentOnUtc = DateTime.UtcNow; result = true; diff --git a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs index b31f3b4ce3..1af83916e8 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesClearTask.cs @@ -1,29 +1,32 @@ using System; -using System.Linq; -using System.Linq.Expressions; +using System.Threading.Tasks; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Messages; -using SmartStore.Data; using SmartStore.Services.Tasks; namespace SmartStore.Services.Messages { /// - /// Represents a task for deleting sent emails from the message queue + /// Represents a task for deleting sent emails from the message queue. /// - public partial class QueuedMessagesClearTask : ITask + public partial class QueuedMessagesClearTask : AsyncTask { private readonly IRepository _qeRepository; + private readonly CommonSettings _commonSettings; - public QueuedMessagesClearTask(IRepository qeRepository) + public QueuedMessagesClearTask( + IRepository qeRepository, + CommonSettings commonSettings) { - this._qeRepository = qeRepository; + _qeRepository = qeRepository; + _commonSettings = commonSettings; } - public void Execute(TaskExecutionContext ctx) + public override async Task ExecuteAsync(TaskExecutionContext ctx) { - var olderThan = DateTime.UtcNow.AddDays(-14); - _qeRepository.DeleteAll(x => x.SentOnUtc.HasValue && x.CreatedOnUtc < olderThan); + var olderThan = DateTime.UtcNow.AddDays(-Math.Abs(_commonSettings.MaxQueuedMessagesAgeInDays)); + await _qeRepository.DeleteAllAsync(x => x.SentOnUtc.HasValue && x.CreatedOnUtc < olderThan); _qeRepository.Context.ShrinkDatabase(); } diff --git a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs index 0c3e7080a6..abcd1c2315 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs @@ -1,11 +1,13 @@ -using SmartStore.Services.Tasks; +using System.Threading.Tasks; +using System.Data.Entity; +using SmartStore.Services.Tasks; namespace SmartStore.Services.Messages { /// /// Represents a task for sending queued message /// - public partial class QueuedMessagesSendTask : ITask + public partial class QueuedMessagesSendTask : AsyncTask { private readonly IQueuedEmailService _queuedEmailService; @@ -14,7 +16,7 @@ public QueuedMessagesSendTask(IQueuedEmailService queuedEmailService) _queuedEmailService = queuedEmailService; } - public void Execute(TaskExecutionContext ctx) + public override async Task ExecuteAsync(TaskExecutionContext ctx) { const int pageSize = 1000; const int maxTries = 3; @@ -30,11 +32,12 @@ public void Execute(TaskExecutionContext ctx) UnsentOnly = true, SendManually = false }; - var queuedEmails = _queuedEmailService.SearchEmails(q); + + var queuedEmails = await _queuedEmailService.SearchEmails(q).LoadAsync(); foreach (var queuedEmail in queuedEmails) { - _queuedEmailService.SendEmail(queuedEmail); + await _queuedEmailService.SendEmailAsync(queuedEmail); } if (!queuedEmails.HasNextPage) diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs index 9a8029e57f..627a5b692d 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeFormatter.cs @@ -96,13 +96,13 @@ 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); caAttribute = string.Format("{0}: {1}", attributeName, - HtmlUtils.FormatText(valueStr.EmptyNull().Replace(":", ""), false, true, false, false, false, false)); + HtmlUtils.ConvertPlainTextToHtml(valueStr.EmptyNull().Replace(":", "").HtmlEncode())); //we never encode multiline textbox input } @@ -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/IOrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs index 9c3f825b03..5ac6d24daf 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs @@ -2,6 +2,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; using SmartStore.Services.Payments; namespace SmartStore.Services.Orders @@ -16,7 +17,35 @@ public partial interface IOrderProcessingService /// /// Order to be validated void CheckOrderStatus(Order order); - + + /// + /// Checks whether orders are placed at too short intervals. + /// + /// Customer. + /// Store + /// true valid interval, false invalid interval. + bool IsMinimumOrderPlacementIntervalValid(Customer customer, Store store); + + /// + /// Checks whether an order can be placed. + /// + /// Process payment request. + /// List of warning messages. Empty list if an order can be placed. + IList GetOrderPlacementWarnings(ProcessPaymentRequest processPaymentRequest); + + /// + /// Checks whether an order can be placed. + /// + /// Process payment request. + /// Initial order if any (recurring payment). + /// Customer placing the order. + /// List of warning messages. Empty list if an order can be placed. + IList GetOrderPlacementWarnings( + ProcessPaymentRequest processPaymentRequest, + Order initialOrder, + Customer customer, + out IList cart); + /// /// Places an order /// 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/IOrderService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs index 9fca02b17f..e0d8dddfc4 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs @@ -83,6 +83,7 @@ public partial interface IOrderService /// Billing email. Leave empty to load all records. /// Filter by order number /// Billing name. Leave empty to load all records. + /// Filter by payment method system names. /// Order query IQueryable GetOrders( int storeId, @@ -94,7 +95,8 @@ IQueryable GetOrders( int[] shippingStatusIds, string billingEmail, string orderNumber, - string billingName = null); + string billingName = null, + string[] paymentMethods = null); /// /// Search orders @@ -112,10 +114,12 @@ IQueryable GetOrders( /// Page index /// Page size /// Billing name. Leave empty to load all records. + /// Filter by payment method system names. /// Order collection IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, DateTime? endTime, int[] orderStatusIds, int[] paymentStatusIds, int[] shippingStatusIds, - string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null); + string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null, + string[] paymentMethods = null); /// /// Gets all orders by affiliate identifier @@ -126,12 +130,6 @@ IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, /// Orders IPagedList GetAllOrders(int affiliateId, int pageIndex, int pageSize); - /// - /// Load all orders - /// - /// Order collection - IList LoadAllOrders(); - /// /// Gets all orders by affiliate identifier /// 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/OrderExtensions.cs b/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs index e896a7c12e..5a5803d728 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderExtensions.cs @@ -40,7 +40,7 @@ public static string FormatOrderNoteText(this OrderNote orderNote) if (orderNote.Note.IsEmpty()) return string.Empty; - return HtmlUtils.FormatText(orderNote.Note, false, true, true, false, false, false); + return HtmlUtils.ConvertPlainTextToHtml(orderNote.Note); } public static List GetBundleData(this OrderItem orderItem) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 40b07cabe8..f09a198d95 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -12,6 +12,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; using SmartStore.Core.Localization; @@ -73,12 +74,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 +116,8 @@ public OrderProcessingService( TaxSettings taxSettings, LocalizationSettings localizationSettings, CurrencySettings currencySettings, - ShoppingCartSettings shoppingCartSettings) + ShoppingCartSettings shoppingCartSettings, + CatalogSettings catalogSettings) { _orderService = orderService; _webHelper = webHelper; @@ -152,6 +155,7 @@ public OrderProcessingService( _localizationSettings = localizationSettings; _currencySettings = currencySettings; _shoppingCartSettings = shoppingCartSettings; + _catalogSettings = catalogSettings; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -423,45 +427,321 @@ public void CheckOrderStatus(Order order) #region Methods - /// - /// Places an order - /// - /// Process payment request - /// Place order result + public virtual bool IsMinimumOrderPlacementIntervalValid(Customer customer, Store store) + { + Guard.NotNull(customer, nameof(customer)); + Guard.NotNull(store, nameof(store)); + + // Prevent 2 orders being placed within an X seconds time frame. + if (_orderSettings.MinimumOrderPlacementInterval == 0) + { + return true; + } + + var lastOrder = _orderService.SearchOrders(store.Id, customer.Id, null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); + if (lastOrder == null) + { + return true; + } + + var interval = DateTime.UtcNow - lastOrder.CreatedOnUtc; + return interval.TotalSeconds > _orderSettings.MinimumOrderPlacementInterval; + } + + public virtual IList GetOrderPlacementWarnings(ProcessPaymentRequest processPaymentRequest) + { + Guard.NotNull(processPaymentRequest, nameof(processPaymentRequest)); + + var initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); + var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + + return GetOrderPlacementWarnings(processPaymentRequest, initialOrder, customer, out var cart); + } + + public virtual IList GetOrderPlacementWarnings( + ProcessPaymentRequest processPaymentRequest, + Order initialOrder, + Customer customer, + out IList cart) + { + Guard.NotNull(processPaymentRequest, nameof(processPaymentRequest)); + + cart = null; + + var warnings = new List(); + var skipPaymentWorkflow = false; + var isRecurringShoppingCart = false; + var paymentMethodSystemName = processPaymentRequest.PaymentMethodSystemName; + + if (customer == null) + { + warnings.Add(T("Customer.DoesNotExist")); + return warnings; + } + + // Check whether guest checkout is allowed. + if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) + { + warnings.Add(T("Checkout.AnonymousNotAllowed")); + return warnings; + } + + if (!processPaymentRequest.IsRecurringPayment) + { + if (processPaymentRequest.ShoppingCartItemIds.Any()) + { + cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId) + .Where(x => processPaymentRequest.ShoppingCartItemIds.Contains(x.Item.Id)) + .ToList(); + } + else + { + cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); + } + + if (!cart.Any()) + { + warnings.Add(T("ShoppingCart.CartIsEmpty")); + return warnings; + } + + // Validate the entire shopping cart. + var cartWarnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); + if (cartWarnings.Any()) + { + warnings.AddRange(cartWarnings); + return warnings; + } + + // Validate individual cart items. + foreach (var sci in cart) + { + var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(customer, sci.Item.ShoppingCartType, + sci.Item.Product, processPaymentRequest.StoreId, sci.Item.AttributesXml, + sci.Item.CustomerEnteredPrice, sci.Item.Quantity, false, childItems: sci.ChildItems); + + if (sciWarnings.Any()) + { + warnings.AddRange(sciWarnings); + return warnings; + } + } + + // Min totals validation. + var minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); + if (!minOrderSubtotalAmountOk) + { + var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); + warnings.Add(T("Checkout.MinOrderSubtotalAmount", _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); + return warnings; + } + + var minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); + if (!minOrderTotalAmountOk) + { + var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); + warnings.Add(T("Checkout.MinOrderTotalAmount", _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); + return warnings; + } + + // Total validations. + var orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out var orderShippingTaxRate, out var shippingTotalDiscount); + var orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, false); + + if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) + { + warnings.Add(T("Order.CannotCalculateShippingTotal")); + return warnings; + } + + var cartTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (!cartTotal.TotalAmount.HasValue) + { + warnings.Add(T("Order.CannotCalculateOrderTotal")); + return warnings; + } + + skipPaymentWorkflow = cartTotal.TotalAmount.Value == decimal.Zero; + + // Address validations. + if (customer.BillingAddress == null) + { + warnings.Add(T("Order.BillingAddressMissing")); + } + else if (!customer.BillingAddress.Email.IsEmail()) + { + warnings.Add(T("Common.Error.InvalidEmail")); + } + else if (customer.BillingAddress.Country != null && !customer.BillingAddress.Country.AllowsBilling) + { + warnings.Add(T("Order.CountryNotAllowedForBilling", customer.BillingAddress.Country.Name)); + } + + if (cart.RequiresShipping()) + { + if (customer.ShippingAddress == null) + { + warnings.Add(T("Order.ShippingAddressMissing")); + throw new SmartException(); + } + else if (!customer.ShippingAddress.Email.IsEmail()) + { + warnings.Add(T("Common.Error.InvalidEmail")); + } + else if (customer.ShippingAddress.Country != null && !customer.ShippingAddress.Country.AllowsShipping) + { + warnings.Add(T("Order.CountryNotAllowedForShipping", customer.ShippingAddress.Country.Name)); + } + } + } + else + { + // Recurring order. + if (initialOrder == null) + { + warnings.Add(T("Order.InitialOrderDoesNotExistForRecurringPayment")); + return warnings; + } + + var cartTotal = new ShoppingCartTotal(initialOrder.OrderTotal); + skipPaymentWorkflow = cartTotal.TotalAmount.Value == decimal.Zero; + paymentMethodSystemName = initialOrder.PaymentMethodSystemName; + + // Address validations. + if (initialOrder.BillingAddress == null) + { + warnings.Add(T("Order.BillingAddressMissing")); + } + else if (initialOrder.BillingAddress.Country != null && !initialOrder.BillingAddress.Country.AllowsBilling) + { + warnings.Add(T("Order.CountryNotAllowedForBilling", initialOrder.BillingAddress.Country.Name)); + } + + if (initialOrder.ShippingStatus != ShippingStatus.ShippingNotRequired) + { + if (initialOrder.ShippingAddress == null) + { + warnings.Add(T("Order.ShippingAddressMissing")); + } + else if (initialOrder.ShippingAddress.Country != null && !initialOrder.ShippingAddress.Country.AllowsShipping) + { + warnings.Add(T("Order.CountryNotAllowedForShipping", initialOrder.ShippingAddress.Country.Name)); + } + } + } + + // Payment. + if (!warnings.Any() && !skipPaymentWorkflow) + { + var paymentMethod = _paymentService.LoadPaymentMethodBySystemName(paymentMethodSystemName); + if (paymentMethod == null) + { + warnings.Add(T("Payment.CouldNotLoadMethod")); + } + else if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) + { + warnings.Add(T("Payment.MethodNotAvailable")); + } + } + + // Recurring or standard shopping cart? + if (!warnings.Any() && !processPaymentRequest.IsRecurringPayment) + { + isRecurringShoppingCart = cart.IsRecurring(); + if (isRecurringShoppingCart) + { + var recurringCyclesError = cart.GetRecurringCycleInfo(_localizationService, out var recurringCycleLength, out var recurringCyclePeriod, out var recurringTotalCycles); + if (recurringCyclesError.HasValue()) + { + warnings.Add(recurringCyclesError); + } + } + } + else + { + isRecurringShoppingCart = true; + } + + // Validate recurring payment type. + if (!warnings.Any() && !skipPaymentWorkflow && !processPaymentRequest.IsMultiOrder) + { + RecurringPaymentType? recurringPaymentType = null; + + if (!processPaymentRequest.IsRecurringPayment) + { + if (isRecurringShoppingCart) + { + recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); + } + } + else + { + if (isRecurringShoppingCart) + { + recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); + } + else + { + warnings.Add(T("Order.NoRecurringProducts")); + } + } + + if (recurringPaymentType.HasValue) + { + switch (recurringPaymentType.Value) + { + case RecurringPaymentType.NotSupported: + warnings.Add(T("Payment.RecurringPaymentNotSupported")); + break; + case RecurringPaymentType.Manual: + case RecurringPaymentType.Automatic: + break; + default: + warnings.Add(T("Payment.RecurringPaymentTypeUnknown")); + break; + } + } + } + + return warnings; + } + public virtual PlaceOrderResult PlaceOrder( ProcessPaymentRequest processPaymentRequest, Dictionary extraData) { - // think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method - if (processPaymentRequest == null) - throw new ArgumentNullException("processPaymentRequest"); + // Think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method. + Guard.NotNull(processPaymentRequest, nameof(processPaymentRequest)); if (processPaymentRequest.OrderGuid == Guid.Empty) + { processPaymentRequest.OrderGuid = Guid.NewGuid(); + } var result = new PlaceOrderResult(); var utcNow = DateTime.UtcNow; try { - #region Order details (customer, totals) - - // Recurring orders. Load initial order var initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); - if (processPaymentRequest.IsRecurringPayment) + var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); + + var warnings = GetOrderPlacementWarnings(processPaymentRequest, initialOrder, customer, out var cart); + if (warnings.Any()) { - if (initialOrder == null) - throw new ArgumentException(T("Order.InitialOrderDoesNotExistForRecurringPayment")); + result.Errors.AddRange(warnings); + Logger.Warn(string.Join(" ", result.Errors)); + return result; + } + #region Order details + + if (processPaymentRequest.IsRecurringPayment) + { processPaymentRequest.PaymentMethodSystemName = initialOrder.PaymentMethodSystemName; } - // customer - var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); - if (customer == null) - throw new ArgumentException(T("Customer.DoesNotExist")); - - // affilites + // Affilites. var affiliateId = 0; var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); if (affiliate != null && affiliate.Active && !affiliate.Deleted) @@ -469,9 +749,9 @@ public virtual PlaceOrderResult PlaceOrder( affiliateId = affiliate.Id; } - // customer currency - string customerCurrencyCode = ""; - decimal customerCurrencyRate = decimal.Zero; + // Customer currency. + var customerCurrencyCode = string.Empty; + var customerCurrencyRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) { var currencyTmp = _currencyService.GetCurrencyById(customer.GetAttribute(SystemCustomerAttributeNames.CurrencyId, processPaymentRequest.StoreId)); @@ -479,7 +759,6 @@ public virtual PlaceOrderResult PlaceOrder( customerCurrencyCode = customerCurrency.CurrencyCode; var primaryStoreCurrency = _storeContext.CurrentStore.PrimaryStoreCurrency; - customerCurrencyRate = customerCurrency.Rate / primaryStoreCurrency.Rate; } else @@ -488,7 +767,7 @@ public virtual PlaceOrderResult PlaceOrder( customerCurrencyRate = initialOrder.CurrencyRate; } - // customer language + // Customer language. Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { @@ -503,66 +782,8 @@ public virtual PlaceOrderResult PlaceOrder( { customerLanguage = _workContext.WorkingLanguage; } - - // check whether customer is guest - if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new SmartException(T("Checkout.AnonymousNotAllowed")); - - var storeId = _storeContext.CurrentStore.Id; - - // load and validate customer shopping cart - IList cart = null; - if (!processPaymentRequest.IsRecurringPayment) - { - //load shopping cart - if (processPaymentRequest.ShoppingCartItemIds.Count > 0) - { - cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId) - .Where(x => processPaymentRequest.ShoppingCartItemIds.Contains(x.Item.Id)) - .ToList(); - } - else - cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); - - if (cart.Count == 0) - throw new SmartException(T("ShoppingCart.CartIsEmpty")); - - // validate the entire shopping cart - var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); - if (warnings.Count > 0) - throw new SmartException(string.Join(" ", warnings)); - - // validate individual cart items - foreach (var sci in cart) - { - var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(customer, sci.Item.ShoppingCartType, - sci.Item.Product, processPaymentRequest.StoreId, sci.Item.AttributesXml, - sci.Item.CustomerEnteredPrice, sci.Item.Quantity, false, childItems: sci.ChildItems); - - if (sciWarnings.Count > 0) - throw new SmartException(string.Join(" ", sciWarnings)); - } - } - - // min totals validation - if (!processPaymentRequest.IsRecurringPayment) - { - var minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); - if (!minOrderSubtotalAmountOk) - { - var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); - throw new SmartException(T("Checkout.MinOrderSubtotalAmount", _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); - } - - var minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); - if (!minOrderTotalAmountOk) - { - var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); - throw new SmartException(T("Checkout.MinOrderTotalAmount", _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); - } - } - - // tax display type + + // Tax display type. var customerTaxDisplayType = TaxDisplayType.IncludingTax; if (!processPaymentRequest.IsRecurringPayment) { @@ -573,7 +794,7 @@ public virtual PlaceOrderResult PlaceOrder( customerTaxDisplayType = initialOrder.CustomerTaxDisplayType; } - // checkout attributes + // Checkout attributes. string checkoutAttributeDescription, checkoutAttributesXml; if (!processPaymentRequest.IsRecurringPayment) { @@ -586,38 +807,28 @@ public virtual PlaceOrderResult PlaceOrder( checkoutAttributeDescription = initialOrder.CheckoutAttributeDescription; } - // applied discount (used to store discount usage history) + // Applied discount (used to store discount usage history). var appliedDiscounts = new List(); - - // sub total decimal orderSubTotalInclTax, orderSubTotalExclTax; decimal orderSubTotalDiscountInclTax = 0, orderSubTotalDiscountExclTax = 0; if (!processPaymentRequest.IsRecurringPayment) { - // sub total (incl tax) - decimal orderSubTotalDiscountAmount1 = decimal.Zero; - Discount orderSubTotalAppliedDiscount1 = null; - decimal subTotalWithoutDiscountBase1 = decimal.Zero; - decimal subTotalWithDiscountBase1 = decimal.Zero; - - _orderTotalCalculationService.GetShoppingCartSubTotal(cart, - true, out orderSubTotalDiscountAmount1, out orderSubTotalAppliedDiscount1, out subTotalWithoutDiscountBase1, out subTotalWithDiscountBase1); + // Sub total (incl tax). + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, true, + out var orderSubTotalDiscountAmount1, out var orderSubTotalAppliedDiscount1, out var subTotalWithoutDiscountBase1, out var subTotalWithDiscountBase1); orderSubTotalInclTax = subTotalWithoutDiscountBase1; orderSubTotalDiscountInclTax = orderSubTotalDiscountAmount1; - // discount history + // Discount history. if (orderSubTotalAppliedDiscount1 != null && !appliedDiscounts.Any(x => x.Id == orderSubTotalAppliedDiscount1.Id)) + { appliedDiscounts.Add(orderSubTotalAppliedDiscount1); + } - // sub total (excl tax) - decimal orderSubTotalDiscountAmount2 = decimal.Zero; - Discount orderSubTotalAppliedDiscount2 = null; - decimal subTotalWithoutDiscountBase2 = decimal.Zero; - decimal subTotalWithDiscountBase2 = decimal.Zero; - - _orderTotalCalculationService.GetShoppingCartSubTotal(cart, - false, out orderSubTotalDiscountAmount2, out orderSubTotalAppliedDiscount2, out subTotalWithoutDiscountBase2, out subTotalWithDiscountBase2); + // Sub total (excl tax). + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, false, + out var orderSubTotalDiscountAmount2, out var orderSubTotalAppliedDiscount2, out var subTotalWithoutDiscountBase2, out var subTotalWithDiscountBase2); orderSubTotalExclTax = subTotalWithoutDiscountBase2; orderSubTotalDiscountExclTax = orderSubTotalDiscountAmount2; @@ -629,8 +840,8 @@ public virtual PlaceOrderResult PlaceOrder( } - // shipping info - bool shoppingCartRequiresShipping = false; + // Shipping info. + var shoppingCartRequiresShipping = false; if (!processPaymentRequest.IsRecurringPayment) { shoppingCartRequiresShipping = cart.RequiresShipping(); @@ -659,19 +870,18 @@ public virtual PlaceOrderResult PlaceOrder( } } - // shipping total + // Shipping total. decimal? orderShippingTotalInclTax, orderShippingTotalExclTax = null; decimal orderShippingTaxRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) { - Discount shippingTotalDiscount = null; - orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out orderShippingTaxRate, out shippingTotalDiscount); + orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out orderShippingTaxRate, out var shippingTotalDiscount); orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, false); - if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) - throw new SmartException(T("Order.CannotCalculateShippingTotal")); if (shippingTotalDiscount != null && !appliedDiscounts.Any(x => x.Id == shippingTotalDiscount.Id)) + { appliedDiscounts.Add(shippingTotalDiscount); + } } else { @@ -680,11 +890,11 @@ public virtual PlaceOrderResult PlaceOrder( orderShippingTaxRate = initialOrder.OrderShippingTaxRate; } - //payment total + // Payment total. decimal paymentAdditionalFeeInclTax, paymentAdditionalFeeExclTax, paymentAdditionalFeeTaxRate; if (!processPaymentRequest.IsRecurringPayment) { - decimal paymentAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, processPaymentRequest.PaymentMethodSystemName); + var paymentAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart, processPaymentRequest.PaymentMethodSystemName); paymentAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, true, customer, out paymentAdditionalFeeTaxRate); paymentAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentAdditionalFee, false, customer); } @@ -695,16 +905,15 @@ public virtual PlaceOrderResult PlaceOrder( paymentAdditionalFeeTaxRate = initialOrder.PaymentMethodAdditionalFeeTaxRate; } - //tax total - decimal orderTaxTotal = decimal.Zero; + // Tax total. + var orderTaxTotal = decimal.Zero; string vatNumber = "", taxRates = ""; if (!processPaymentRequest.IsRecurringPayment) { - //tax amount - SortedDictionary taxRatesDictionary = null; - orderTaxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRatesDictionary); + // Tax amount. + orderTaxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out var taxRatesDictionary); - //VAT number + // VAT number. var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) { @@ -716,22 +925,19 @@ public virtual PlaceOrderResult PlaceOrder( else { orderTaxTotal = initialOrder.OrderTax; - // VAT number vatNumber = initialOrder.VatNumber; } + processPaymentRequest.OrderTax = orderTaxTotal; - // Order total (and applied discounts, gift cards, reward points) + // Order total (and applied discounts, gift cards, reward points). ShoppingCartTotal cartTotal = null; if (!processPaymentRequest.IsRecurringPayment) { cartTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart); - if (!cartTotal.TotalAmount.HasValue) - throw new SmartException(T("Order.CannotCalculateOrderTotal")); - - // Discount history + // Discount history. if (cartTotal.AppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == cartTotal.AppliedDiscount.Id)) { appliedDiscounts.Add(cartTotal.AppliedDiscount); @@ -747,9 +953,9 @@ public virtual PlaceOrderResult PlaceOrder( #endregion - #region Addresses & pre payment workflow + #region Addresses & pre-payment workflow - // give payment processor the opportunity to fullfill billing address + // Give payment processor the opportunity to fullfill billing address. var preProcessPaymentResult = _paymentService.PreProcessPayment(processPaymentRequest); if (!preProcessPaymentResult.Success) @@ -759,95 +965,44 @@ public virtual PlaceOrderResult PlaceOrder( return result; } - Address billingAddress = null; - if (!processPaymentRequest.IsRecurringPayment) - { - if (customer.BillingAddress == null) - throw new SmartException(T("Order.BillingAddressMissing")); - - if (!customer.BillingAddress.Email.IsEmail()) - throw new SmartException(T("Common.Error.InvalidEmail")); - - billingAddress = (Address)customer.BillingAddress.Clone(); - } - else - { - if (initialOrder.BillingAddress == null) - throw new SmartException(T("Order.BillingAddressMissing")); - - billingAddress = (Address)initialOrder.BillingAddress.Clone(); - } - - if (billingAddress.Country != null && !billingAddress.Country.AllowsBilling) - throw new SmartException(T("Order.CountryNotAllowedForBilling", billingAddress.Country.Name)); + var billingAddress = !processPaymentRequest.IsRecurringPayment + ? (Address)customer.BillingAddress.Clone() + : (Address)initialOrder.BillingAddress.Clone(); Address shippingAddress = null; if (shoppingCartRequiresShipping) { - if (!processPaymentRequest.IsRecurringPayment) - { - if (customer.ShippingAddress == null) - throw new SmartException(T("Order.ShippingAddressMissing")); - - if (!customer.ShippingAddress.Email.IsEmail()) - throw new SmartException(T("Common.Error.InvalidEmail")); - - shippingAddress = (Address)customer.ShippingAddress.Clone(); - } - else - { - if (initialOrder.ShippingAddress == null) - throw new SmartException(T("Order.ShippingAddressMissing")); - - shippingAddress = (Address)initialOrder.ShippingAddress.Clone(); - } - - if (shippingAddress.Country != null && !shippingAddress.Country.AllowsShipping) - throw new SmartException(T("Order.CountryNotAllowedForShipping", shippingAddress.Country.Name)); + shippingAddress = !processPaymentRequest.IsRecurringPayment + ? (Address)customer.ShippingAddress.Clone() + : (Address)initialOrder.ShippingAddress.Clone(); } #endregion #region Payment workflow - // skip payment workflow if order total equals zero - var skipPaymentWorkflow = false; - if (cartTotal.TotalAmount.Value == decimal.Zero) - { - skipPaymentWorkflow = true; - } + // Skip payment workflow if order total equals zero. + var skipPaymentWorkflow = cartTotal.TotalAmount.Value == decimal.Zero; - // payment workflow + // Payment workflow. Provider paymentMethod = null; if (!skipPaymentWorkflow) { paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new SmartException(T("Payment.CouldNotLoadMethod")); - - // ensure that payment method is active - if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new SmartException(T("Payment.MethodNotAvailable")); } else { processPaymentRequest.PaymentMethodSystemName = ""; } - // recurring or standard shopping cart? + // Recurring or standard shopping cart? var isRecurringShoppingCart = false; if (!processPaymentRequest.IsRecurringPayment) { isRecurringShoppingCart = cart.IsRecurring(); if (isRecurringShoppingCart) { - var recurringCycleLength = 0; - var recurringTotalCycles = 0; - RecurringProductCyclePeriod recurringCyclePeriod; - string recurringCyclesError = cart.GetRecurringCycleInfo(_localizationService, out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); - - if (!string.IsNullOrEmpty(recurringCyclesError)) - throw new SmartException(recurringCyclesError); + var unused = cart.GetRecurringCycleInfo(_localizationService, out var recurringCycleLength, out var recurringCyclePeriod, out var recurringTotalCycles); processPaymentRequest.RecurringCycleLength = recurringCycleLength; processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; @@ -859,7 +1014,7 @@ public virtual PlaceOrderResult PlaceOrder( isRecurringShoppingCart = true; } - // process payment + // Process payment. ProcessPaymentResult processPaymentResult = null; if (!skipPaymentWorkflow && !processPaymentRequest.IsMultiOrder) { @@ -867,7 +1022,7 @@ public virtual PlaceOrderResult PlaceOrder( { if (isRecurringShoppingCart) { - // recurring cart + // Recurring cart. var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { @@ -883,7 +1038,7 @@ public virtual PlaceOrderResult PlaceOrder( } else { - // standard cart + // Standard cart. processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); } } @@ -891,11 +1046,11 @@ public virtual PlaceOrderResult PlaceOrder( { if (isRecurringShoppingCart) { - // Old credit card info + // Old credit card info. processPaymentRequest.CreditCardType = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardType) : ""; processPaymentRequest.CreditCardName = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardName) : ""; processPaymentRequest.CreditCardNumber = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardNumber) : ""; - // MaskedCreditCardNumber + // MaskedCreditCardNumber. processPaymentRequest.CreditCardCvv2 = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardCvv2) : ""; try @@ -914,7 +1069,7 @@ public virtual PlaceOrderResult PlaceOrder( processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; case RecurringPaymentType.Automatic: - //payment is processed on payment gateway site + // Payment is processed on payment gateway site. processPaymentResult = new ProcessPaymentResult(); break; default: @@ -929,20 +1084,19 @@ public virtual PlaceOrderResult PlaceOrder( } else { - // payment is not required - if (processPaymentResult == null) - { - processPaymentResult = new ProcessPaymentResult(); - } - processPaymentResult.NewPaymentStatus = PaymentStatus.Paid; + // Payment is not required. + processPaymentResult = new ProcessPaymentResult + { + NewPaymentStatus = PaymentStatus.Paid + }; } #endregion if (processPaymentResult.Success) { - //save order in data storage - //uncomment this line to support transactions + // Save order. + // Uncomment this line to support transactions. //using (var scope = new System.Transactions.TransactionScope()) { #region Save order details @@ -977,6 +1131,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 +1184,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 +1199,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 +1210,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 +1238,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(); @@ -1103,13 +1263,16 @@ public virtual PlaceOrderResult PlaceOrder( order.OrderItems.Add(orderItem); _orderService.UpdateOrder(order); - // Gift cards + // 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++) { @@ -1135,7 +1298,7 @@ public virtual PlaceOrderResult PlaceOrder( _productService.AdjustInventory(sc, true); } - // Clear shopping cart + // Clear shopping cart. if (!processPaymentRequest.IsMultiOrder) { cart.ToList().ForEach(sci => _shoppingCartService.DeleteShoppingCartItem(sci.Item, false)); @@ -1143,11 +1306,11 @@ public virtual PlaceOrderResult PlaceOrder( } else { - // Recurring payment + // Recurring payment. var initialOrderItems = initialOrder.OrderItems; foreach (var orderItem in initialOrderItems) { - // Save item + // Save item. var newOrderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), @@ -1168,18 +1331,23 @@ 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); - // Gift cards + // 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++) { @@ -1206,7 +1374,7 @@ public virtual PlaceOrderResult PlaceOrder( } } - // Discount usage history + // Discount usage history. if (!processPaymentRequest.IsRecurringPayment) { foreach (var discount in appliedDiscounts) @@ -1221,7 +1389,7 @@ public virtual PlaceOrderResult PlaceOrder( } } - // Gift card usage history + // Gift card usage history. if (!processPaymentRequest.IsRecurringPayment && cartTotal.AppliedGiftCards != null) { foreach (var agc in cartTotal.AppliedGiftCards) @@ -1239,7 +1407,7 @@ public virtual PlaceOrderResult PlaceOrder( } } - // Reward points history + // Reward points history. if (cartTotal.RedeemedRewardPointsAmount > decimal.Zero) { customer.AddRewardPointsHistoryEntry(-cartTotal.RedeemedRewardPoints, @@ -1250,10 +1418,10 @@ public virtual PlaceOrderResult PlaceOrder( _customerService.UpdateCustomer(customer); } - // Recurring orders + // Recurring orders. if (!processPaymentRequest.IsRecurringPayment && isRecurringShoppingCart) { - // Create recurring payment (the first payment) + // Create recurring payment (the first payment). var rp = new RecurringPayment { CycleLength = processPaymentRequest.RecurringCycleLength, @@ -1270,26 +1438,22 @@ public virtual PlaceOrderResult PlaceOrder( switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: - { - //not supported - } break; case RecurringPaymentType.Manual: { - //first payment - var rph = new RecurringPaymentHistory() + // First payment. + rp.RecurringPaymentHistory.Add(new RecurringPaymentHistory { RecurringPayment = rp, - CreatedOnUtc = utcNow, - OrderId = order.Id, - }; - rp.RecurringPaymentHistory.Add(rph); + CreatedOnUtc = utcNow, + OrderId = order.Id + }); _orderService.UpdateRecurringPayment(rp); } break; case RecurringPaymentType.Automatic: { - //will be created later (process is automated) + // Will be created later (process is automated). } break; default: @@ -1301,10 +1465,10 @@ public virtual PlaceOrderResult PlaceOrder( #region Notifications, notes and attributes - // notes, messages + // 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) { @@ -1317,25 +1481,25 @@ public virtual PlaceOrderResult PlaceOrder( _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerEmailQueued", msg.Email.Id)); } - // check order status + // Check order status. CheckOrderStatus(order); - //reset checkout data + // 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 + // Check for generic attributes to be inserted automatically. foreach (var customProperty in processPaymentRequest.CustomProperties.Where(x => x.Key.HasValue() && x.Value.AutoCreateGenericAttribute)) { _genericAttributeService.SaveAttribute(order, customProperty.Key, customProperty.Value.Value, order.StoreId); } - //uncomment this line to support transactions + // Uncomment this line to support transactions. //scope.Complete(); - // raise event + // Publish events. _eventPublisher.PublishOrderPlaced(order); if (!processPaymentRequest.IsRecurringPayment) @@ -1343,7 +1507,6 @@ public virtual PlaceOrderResult PlaceOrder( _customerActivityService.InsertActivity("PublicStore.PlaceOrder", T("ActivityLog.PublicStore.PlaceOrder", order.GetOrderNumber())); } - //raise event if (order.PaymentStatus == PaymentStatus.Paid) { _eventPublisher.PublishOrderPaid(order); @@ -1361,10 +1524,7 @@ public virtual PlaceOrderResult PlaceOrder( if (nsResult.HasValue) { - if (nsResult.Value) - _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionAdded")); - else - _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionRemoved")); + _orderService.AddOrderNote(order, T(nsResult.Value ? "Admin.OrderNotice.NewsLetterSubscriptionAdded" : "Admin.OrderNotice.NewsLetterSubscriptionRemoved")); } } @@ -1387,7 +1547,7 @@ public virtual PlaceOrderResult PlaceOrder( result.AddError(ex.Message); } - if (result.Errors.Count > 0) + if (result.Errors.Any()) { Logger.Error(string.Join(" ", result.Errors)); } @@ -1401,30 +1561,28 @@ public virtual PlaceOrderResult PlaceOrder( /// The order public virtual void DeleteOrder(Order order) { - if (order == null) - throw new ArgumentNullException("order"); - - //reward points - ReduceRewardPoints(order); + Guard.NotNull(order, nameof(order)); - //cancel recurring payments - var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null); - foreach (var rp in recurringPayments) + if (order.OrderStatus != OrderStatus.Cancelled) { - //use errors? - var errors = CancelRecurringPayment(rp); + ReduceRewardPoints(order); + + // Cancel recurring payments. + var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null); + foreach (var rp in recurringPayments) + { + CancelRecurringPayment(rp); + } + + // Adjust inventory. + foreach (var orderItem in order.OrderItems) + { + _productService.AdjustInventory(orderItem, false, orderItem.Quantity); + } } - //Adjust inventory - foreach (var orderItem in order.OrderItems) - { - _productService.AdjustInventory(orderItem, false, orderItem.Quantity); - } + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderDeleted")); - //add a note - _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderDeleted")); - - //now delete an order _orderService.DeleteOrder(order); } @@ -1697,26 +1855,26 @@ public virtual bool CanCancelOrder(Order order) /// True to notify customer public virtual void CancelOrder(Order order, bool notifyCustomer) { - if (order == null) - throw new ArgumentNullException("order"); + Guard.NotNull(order, nameof(order)); if (!CanCancelOrder(order)) + { throw new SmartException(T("Order.CannotCancel")); + } - //Cancel order + // Cancel order. SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderCancelled")); - //cancel recurring payments + // Cancel recurring payments. var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null); foreach (var rp in recurringPayments) { - //use errors? - var errors = CancelRecurringPayment(rp); + CancelRecurringPayment(rp); } - //Adjust inventory + // Adjust inventory. foreach (var orderItem in order.OrderItems) { _productService.AdjustInventory(orderItem, false, orderItem.Quantity); diff --git a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs index f98d7292f0..a39d0e395c 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderReportService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Data.Entity; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; @@ -215,7 +216,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; @@ -248,7 +249,7 @@ group orderItem by orderItem.ProductId into g } if (recordsToReturn != 0 && recordsToReturn != int.MaxValue) - query2 = query2.Take(recordsToReturn); + query2 = query2.Take(() => recordsToReturn); var result = query2.ToList().Select(x => { @@ -264,7 +265,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 +295,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 @@ -295,7 +309,7 @@ group orderItem_p by orderItem_p.p.Id into g query3 = query3.OrderByDescending(x => x.ProductsPurchased); if (recordsToReturn > 0) - query3 = query3.Take(recordsToReturn); + query3 = query3.Take(() => recordsToReturn); var report = query3.ToList(); @@ -318,8 +332,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 +343,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/OrderService.cs b/src/Libraries/SmartStore.Services/Orders/OrderService.cs index 62714bfebe..0d9d8f965d 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderService.cs @@ -10,16 +10,12 @@ using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; +using SmartStore.Data.Caching; namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderService : IOrderService + public partial class OrderService : IOrderService { - #region Fields - private readonly IRepository _orderRepository; private readonly IRepository _orderItemRepository; private readonly IRepository _orderNoteRepository; @@ -29,21 +25,6 @@ public partial class OrderService : IOrderService private readonly IRepository _returnRequestRepository; private readonly IEventPublisher _eventPublisher; - #endregion - - #region Ctor - - /// - /// Ctor - /// - /// Order repository - /// Order item repository - /// Order note repository - /// Product repository - /// Recurring payment repository - /// Customer repository - /// Return request repository - /// Event published public OrderService(IRepository orderRepository, IRepository orderItemRepository, IRepository orderNoteRepository, @@ -63,30 +44,14 @@ public OrderService(IRepository orderRepository, _eventPublisher = eventPublisher; } - #endregion - - #region Methods - - #region Orders - - /// - /// Gets an order - /// - /// The order identifier - /// Order public virtual Order GetOrderById(int orderId) { if (orderId == 0) return null; - return _orderRepository.GetById(orderId); + return _orderRepository.GetByIdCached(orderId, "db.order.id-" + orderId); } - /// - /// Get orders by identifiers - /// - /// Order identifiers - /// Order public virtual IList GetOrdersByIds(int[] orderIds) { if (orderIds == null || orderIds.Length == 0) @@ -122,11 +87,6 @@ public virtual Order GetOrderByNumber(string orderNumber) return order; } - /// - /// Gets an order - /// - /// The order identifier - /// Order public virtual Order GetOrderByGuid(Guid orderGuid) { if (orderGuid == Guid.Empty) @@ -139,12 +99,6 @@ public virtual Order GetOrderByGuid(Guid orderGuid) return order; } - /// - /// Get order by payment authorization data - /// - /// System name of the payment method - /// Authorization transaction Id - /// Order entity public virtual Order GetOrderByPaymentAuthorization(string paymentMethodSystemName, string authorizationTransactionId) { if (paymentMethodSystemName.IsEmpty() || authorizationTransactionId.IsEmpty()) @@ -158,12 +112,6 @@ from o in _orderRepository.Table return query.FirstOrDefault(); } - /// - /// Get order by payment capture data - /// - /// System name of the payment method - /// Capture transaction Id - /// Order entity public virtual Order GetOrderByPaymentCapture(string paymentMethodSystemName, string captureTransactionId) { if (paymentMethodSystemName.IsEmpty() || captureTransactionId.IsEmpty()) @@ -187,7 +135,8 @@ public virtual IQueryable GetOrders( int[] shippingStatusIds, string billingEmail, string orderNumber, - string billingName = null) + string billingName = null, + string[] paymentMethods = null) { var query = _orderRepository.Table; @@ -226,40 +175,29 @@ public virtual IQueryable GetOrders( if (shippingStatusIds != null && shippingStatusIds.Count() > 0) query = query.Where(x => shippingStatusIds.Contains(x.ShippingStatusId)); + if (paymentMethods != null && paymentMethods.Any()) + { + query = query.Where(x => paymentMethods.Contains(x.PaymentMethodSystemName)); + } + query = query.Where(x => !x.Deleted); return query; } - /// - /// Search orders - /// - /// Store identifier; 0 to load all orders - /// Customer identifier; 0 to load all orders - /// Order start time; null to load all orders - /// Order end time; null to load all orders - /// Filter by order status - /// Filter by payment status - /// Filter by shipping status - /// Billing email. Leave empty to load all records. - /// Search by order GUID (Global unique identifier) or part of GUID. Leave empty to load all orders. - /// Filter by order number - /// Page index - /// Page size - /// Billing name. Leave empty to load all records. - /// Order collection public virtual IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, DateTime? endTime, int[] orderStatusIds, int[] paymentStatusIds, int[] shippingStatusIds, - string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null) + string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null, + string[] paymentMethods = null) { var query = GetOrders(storeId, customerId, startTime, endTime, orderStatusIds, paymentStatusIds, shippingStatusIds, - billingEmail, orderNumber, billingName); + billingEmail, orderNumber, billingName, paymentMethods); query = query.OrderByDescending(x => x.CreatedOnUtc); if (orderGuid.HasValue()) { - //filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string + // Filter by GUID. Filter in BLL because EF doesn't support casting of GUID to string var orders = query.ToList(); orders = orders.FindAll(x => x.OrderGuid.ToString().ToLowerInvariant().Contains(orderGuid.ToLowerInvariant())); @@ -267,18 +205,10 @@ public virtual IPagedList SearchOrders(int storeId, int customerId, DateT } else { - //database layer paging return new PagedList(query, pageIndex, pageSize); } } - /// - /// Gets all orders by affiliate identifier - /// - /// Affiliate identifier - /// Page index - /// Page size - /// Orders public virtual IPagedList GetAllOrders(int affiliateId, int pageIndex, int pageSize) { var query = _orderRepository.Table; @@ -290,20 +220,6 @@ public virtual IPagedList GetAllOrders(int affiliateId, int pageIndex, in return orders; } - /// - /// Load all orders - /// - /// Order collection - public virtual IList LoadAllOrders() - { - return SearchOrders(0, 0, null, null, null, null, null, null, null, null, 0, int.MaxValue); - } - - /// - /// Gets all orders by affiliate identifier - /// - /// Affiliate identifier - /// Order collection public virtual IList GetOrdersByAffiliateId(int affiliateId) { var query = from o in _orderRepository.Table @@ -314,10 +230,6 @@ orderby o.CreatedOnUtc descending return orders; } - /// - /// Inserts an order - /// - /// Order public virtual void InsertOrder(Order order) { if (order == null) @@ -326,10 +238,6 @@ public virtual void InsertOrder(Order order) _orderRepository.Insert(order); } - /// - /// Updates the order - /// - /// The order public virtual void UpdateOrder(Order order) { if (order == null) @@ -340,10 +248,6 @@ public virtual void UpdateOrder(Order order) _eventPublisher.PublishOrderUpdated(order); } - /// - /// Deletes an order - /// - /// The order public virtual void DeleteOrder(Order order) { if (order == null) @@ -353,10 +257,6 @@ public virtual void DeleteOrder(Order order) UpdateOrder(order); } - /// - /// Deletes an order note - /// - /// The order note public virtual void DeleteOrderNote(OrderNote orderNote) { if (orderNote == null) @@ -370,12 +270,6 @@ public virtual void DeleteOrderNote(OrderNote orderNote) _eventPublisher.PublishOrderUpdated(order); } - /// - /// Get an order by authorization transaction ID and payment method system name - /// - /// Authorization transaction ID - /// Payment method system name - /// Order public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName) { var query = _orderRepository.Table; @@ -405,15 +299,8 @@ public virtual void AddOrderNote(Order order, string note, bool displayToCustome } } - #endregion - #region Order items - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item public virtual OrderItem GetOrderItemById(int orderItemId) { if (orderItemId == 0) @@ -422,11 +309,6 @@ public virtual OrderItem GetOrderItemById(int orderItemId) return _orderItemRepository.GetById(orderItemId); } - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) { if (orderItemGuid == Guid.Empty) @@ -438,19 +320,7 @@ public virtual OrderItem GetOrderItemByGuid(Guid orderItemGuid) var item = query.FirstOrDefault(); return item; } - - /// - /// Gets all Order items - /// - /// Order identifier; null to load all records - /// Customer identifier; null to load all records - /// Order start time; null to load all records - /// Order end time; null to load all records - /// Order status; null to load all records - /// Order payment status; null to load all records - /// Order shippment status; null to load all records - /// Value indicating whether to load downloadable products only - /// Order collection + public virtual IList GetAllOrderItems(int? orderId, int? customerId, DateTime? startTime, DateTime? endTime, OrderStatus? os, PaymentStatus? ps, ShippingStatus? ss, @@ -505,10 +375,6 @@ where orderIds.Contains(x.OrderId) return map; } - /// - /// Delete an Order item - /// - /// The Order item public virtual void DeleteOrderItem(OrderItem orderItem) { if (orderItem == null) @@ -526,10 +392,6 @@ public virtual void DeleteOrderItem(OrderItem orderItem) #region Recurring payments - /// - /// Deletes a recurring payment - /// - /// Recurring payment public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -539,11 +401,6 @@ public virtual void DeleteRecurringPayment(RecurringPayment recurringPayment) UpdateRecurringPayment(recurringPayment); } - /// - /// Gets a recurring payment - /// - /// The recurring payment identifier - /// Recurring payment public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) { if (recurringPaymentId == 0) @@ -552,10 +409,6 @@ public virtual RecurringPayment GetRecurringPaymentById(int recurringPaymentId) return _recurringPaymentRepository.GetById(recurringPaymentId); } - /// - /// Inserts a recurring payment - /// - /// Recurring payment public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -566,10 +419,6 @@ public virtual void InsertRecurringPayment(RecurringPayment recurringPayment) _eventPublisher.PublishOrderUpdated(recurringPayment.InitialOrder); } - /// - /// Updates the recurring payment - /// - /// Recurring payment public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) { if (recurringPayment == null) @@ -580,15 +429,6 @@ public virtual void UpdateRecurringPayment(RecurringPayment recurringPayment) _eventPublisher.PublishOrderUpdated(recurringPayment.InitialOrder); } - /// - /// Search recurring payments - /// - /// The store identifier; 0 to load all records - /// The customer identifier; 0 to load all records - /// The initial order identifier; 0 to load all records - /// Initial order status identifier; null to load all records - /// A value indicating whether to show hidden records - /// Recurring payment collection public virtual IList SearchRecurringPayments(int storeId, int customerId, int initialOrderId, OrderStatus? initialOrderStatus, bool showHidden = false) @@ -623,10 +463,6 @@ where query1.Contains(rp.Id) #region Return requests - /// - /// Deletes a return request - /// - /// Return request public virtual void DeleteReturnRequest(ReturnRequest returnRequest) { if (returnRequest == null) @@ -640,11 +476,6 @@ public virtual void DeleteReturnRequest(ReturnRequest returnRequest) _eventPublisher.PublishOrderUpdated(orderItem.Order); } - /// - /// Gets a return request - /// - /// Return request identifier - /// Return request public virtual ReturnRequest GetReturnRequestById(int returnRequestId) { if (returnRequestId == 0) @@ -653,17 +484,6 @@ public virtual ReturnRequest GetReturnRequestById(int returnRequestId) return _returnRequestRepository.GetById(returnRequestId); } - /// - /// Search return requests - /// - /// Store identifier; 0 to load all entries - /// Customer identifier; null to load all entries - /// Order item identifier; null to load all entries - /// Return request status; null to load all entries - /// Page index - /// Page size - /// Return request Id - /// Return requests public virtual IPagedList SearchReturnRequests(int storeId, int customerId, int orderItemId, ReturnRequestStatus? rs, int pageIndex, int pageSize, int id = 0) { var query = _returnRequestRepository.Table; @@ -693,7 +513,5 @@ public virtual IPagedList SearchReturnRequests(int storeId, int c } #endregion - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 98d7357f15..7bcf2910a0 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; + var roundingAmount = decimal.Zero; var roundingAmountConverted = decimal.Zero; + // Return null if we have errors: 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..d0880c760d 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); } @@ -936,10 +942,16 @@ public virtual IList GetShoppingCartWarnings(IList a.TextPrompt))) - warnings.Add(ca2.GetLocalized(a => a.TextPrompt)); + string textPrompt = ca2.GetLocalized(a => a.TextPrompt); + + if (textPrompt.HasValue()) + { + warnings.Add(textPrompt); + } else + { warnings.Add(T("ShoppingCart.SelectAttribute", ca2.GetLocalized(a => a.Name))); + } } } } @@ -1008,9 +1020,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 +1178,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 +1208,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/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/Polls/IPollService.cs b/src/Libraries/SmartStore.Services/Polls/IPollService.cs index fb5929cf51..bdf19bab73 100644 --- a/src/Libraries/SmartStore.Services/Polls/IPollService.cs +++ b/src/Libraries/SmartStore.Services/Polls/IPollService.cs @@ -72,5 +72,14 @@ public partial interface IPollService /// Customer identifier /// Result bool AlreadyVoted(int pollId, int customerId); + + /// + /// Get voting records for a poll. + /// + /// Poll identifier. + /// Page index. + /// Page size. + /// List of voting records. + IPagedList GetVotingRecords(int pollId, int pageIndex, int pageSize); } } diff --git a/src/Libraries/SmartStore.Services/Polls/PollService.cs b/src/Libraries/SmartStore.Services/Polls/PollService.cs index 327d3f3075..e12cb2f6af 100644 --- a/src/Libraries/SmartStore.Services/Polls/PollService.cs +++ b/src/Libraries/SmartStore.Services/Polls/PollService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using SmartStore.Core; using SmartStore.Core.Caching; @@ -164,5 +165,27 @@ join pvr in _pollVotingRecords.Table on pa.Id equals pvr.PollAnswerId select pvr).Count() > 0; return result; } + + public virtual IPagedList GetVotingRecords(int pollId, int pageIndex, int pageSize) + { + if (pollId == 0) + { + return new PagedList(new List(), pageIndex, pageSize); + } + + var query = + from pa in _pollAnswerRepository.TableUntracked + join pvr in _pollVotingRecords.TableUntracked on pa.Id equals pvr.PollAnswerId + where pa.PollId == pollId + orderby pa.DisplayOrder, pvr.CreatedOnUtc descending + select pvr; + + query = query + .Expand(x => x.Customer) + .Expand(x => x.PollAnswer); + + var votings = new PagedList(query, pageIndex, pageSize); + return votings; + } } } diff --git a/src/Libraries/SmartStore.Services/ScopedServiceBase.cs b/src/Libraries/SmartStore.Services/ScopedServiceBase.cs index 3bd839061b..961fa5cd6f 100644 --- a/src/Libraries/SmartStore.Services/ScopedServiceBase.cs +++ b/src/Libraries/SmartStore.Services/ScopedServiceBase.cs @@ -5,8 +5,6 @@ namespace SmartStore.Services { public abstract class ScopedServiceBase : IScopedService { - private bool _isInScope; - /// /// Creates a long running unit of work in which cache eviction is suppressed /// @@ -14,18 +12,18 @@ public abstract class ScopedServiceBase : IScopedService /// A disposable unit of work public IDisposable BeginScope(bool clearCache = true) { - if (_isInScope) + if (IsInScope) { // nested batches are not supported return ActionDisposable.Empty; } OnBeginScope(); - _isInScope = true; + IsInScope = true; return new ActionDisposable(() => { - _isInScope = false; + IsInScope = false; OnEndScope(); if (clearCache && HasChanges) { @@ -48,12 +46,13 @@ public bool HasChanges protected bool IsInScope { - get { return _isInScope; } + get; + private set; } public void ClearCache() { - if (!_isInScope) + if (!IsInScope) { OnClearCache(); HasChanges = false; 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 94% rename from src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs index 00be3a01bc..cfc21d3a9f 100644 --- a/src/Libraries/SmartStore.Services/Search/CatalogSearchQuery.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchQuery.cs @@ -37,6 +37,20 @@ object ICloneable.Clone() return this.MemberwiseClone(); } + public bool IsSubPage + { + get + { + if (PageIndex > 0) + { + return true; + } + + var hasActiveFilter = FacetDescriptors.Values.Any(x => x.Values.Any(y => y.IsSelected)); + return hasActiveFilter; + } + } + #region Fluent builder public CatalogSearchQuery SortBy(ProductSortingEnum sort) @@ -192,12 +206,9 @@ public CatalogSearchQuery WithCategoryIds(bool? featuredOnly, params int[] ids) return this; } - string fieldName = null; - - if (featuredOnly.HasValue) - fieldName = (featuredOnly.Value ? "featuredcategoryid" : "notfeaturedcategoryid"); - else - fieldName = "categoryid"; + var fieldName = featuredOnly.HasValue + ? featuredOnly.Value ? "featuredcategoryid" : "notfeaturedcategoryid" + : "categoryid"; return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField(fieldName, x).ExactMatch().NotAnalyzed()).ToArray())); } @@ -222,12 +233,9 @@ public CatalogSearchQuery WithManufacturerIds(bool? featuredOnly, params int[] i return this; } - string fieldName = null; - - if (featuredOnly.HasValue) - fieldName = (featuredOnly.Value ? "featuredmanufacturerid" : "notfeaturedmanufacturerid"); - else - fieldName = "manufacturerid"; + var fieldName = featuredOnly.HasValue + ? featuredOnly.Value ? "featuredmanufacturerid" : "notfeaturedmanufacturerid" + : "manufacturerid"; return WithFilter(SearchFilter.Combined(ids.Select(x => SearchFilter.ByField(fieldName, x).ExactMatch().NotAnalyzed()).ToArray())); } 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..c19da2f48f --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Catalog/CatalogSearchResult.cs @@ -0,0 +1,95 @@ +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 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; } + + /// + /// 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..b790813c4a --- /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 + "Search")) + { + totalCount = searchEngine.Count(); + // Fix paging boundaries + if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) + { + searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); + } + } + + if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) + { + using (_services.Chronometer.Step(stepPrefix + "Hits")) + { + searchHits = searchEngine.Search(); + } + + 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 84% rename from src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs index 6ffa7e6827..fcd96b6675 100644 --- a/src/Libraries/SmartStore.Services/Search/LinqCatalogSearchService.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/LinqCatalogSearchService.cs @@ -1,14 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; +using System.Data.Entity; 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 +16,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,120 +61,31 @@ 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) { - if (ids.Any()) - { - return - from p in query - from pc in p.ProductCategories.Where(pc => ids.Contains(pc.CategoryId)) - where (!featuredOnly.HasValue || featuredOnly.Value == pc.IsFeaturedProduct) - select p; - } - - return query; - } + return + from p in query + from pc in p.ProductCategories.Where(pc => ids.Contains(pc.CategoryId)) + where !featuredOnly.HasValue || featuredOnly.Value == pc.IsFeaturedProduct + select p; + } - private IQueryable QueryManufacturers(IQueryable query, List ids, bool? featuredOnly) + private IQueryable QueryManufacturers(IQueryable query, List ids, bool? featuredOnly) { - if (ids.Any()) - { - return - from p in query - from pm in p.ProductManufacturers.Where(pm => ids.Contains(pm.ManufacturerId)) - where (!featuredOnly.HasValue || featuredOnly.Value == pm.IsFeaturedProduct) - select p; - } - - return query; - } + return + from p in query + from pm in p.ProductManufacturers.Where(pm => ids.Contains(pm.ManufacturerId)) + where !featuredOnly.HasValue || featuredOnly.Value == pm.IsFeaturedProduct + select p; + } - protected virtual IQueryable ApplySearchTerm(IQueryable query, CatalogSearchQuery searchQuery) + protected virtual IQueryable ApplySearchTerm(IQueryable query, CatalogSearchQuery searchQuery) { var term = searchQuery.Term; var fields = searchQuery.Fields; @@ -221,9 +131,11 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ { var ordered = false; var utcNow = DateTime.UtcNow; + var categoryId = 0; + var manufacturerId = 0; 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 @@ -240,9 +152,10 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ var categoryIds = GetIdList(filters, "categoryid"); if (categoryIds.Any()) { - if (categoryIds.Count == 1 && categoryIds.First() == 0) + categoryId = categoryIds.First(); + if (categoryIds.Count == 1 && categoryId == 0) { - // has no category + // Has no category. query = query.Where(x => x.ProductCategories.Count == 0); } else @@ -251,30 +164,51 @@ protected virtual IQueryable GetProductQuery(CatalogSearchQuery searchQ } } - query = QueryCategories(query, GetIdList(filters, "featuredcategoryid"), true); - query = QueryCategories(query, GetIdList(filters, "notfeaturedcategoryid"), false); + var featuredCategoryIds = GetIdList(filters, "featuredcategoryid"); + var notFeaturedCategoryIds = GetIdList(filters, "notfeaturedcategoryid"); + if (featuredCategoryIds.Any()) + { + categoryId = categoryId == 0 ? featuredCategoryIds.First() : categoryId; + query = QueryCategories(query, featuredCategoryIds, true); + } + if (notFeaturedCategoryIds.Any()) + { + categoryId = categoryId == 0 ? notFeaturedCategoryIds.First() : categoryId; + query = QueryCategories(query, notFeaturedCategoryIds, false); + } var manufacturerIds = GetIdList(filters, "manufacturerid"); if (manufacturerIds.Any()) { - if (manufacturerIds.Count == 1 && manufacturerIds.First() == 0) + manufacturerId = manufacturerIds.First(); + if (manufacturerIds.Count == 1 && manufacturerId == 0) { - // has no manufacturer + // Has no manufacturer. query = query.Where(x => x.ProductManufacturers.Count == 0); } else { - query = QueryManufacturers(query, manufacturerIds, null); + query = QueryManufacturers(query, manufacturerIds, null); } } - query = QueryManufacturers(query, GetIdList(filters, "featuredmanufacturerid"), true); - query = QueryManufacturers(query, GetIdList(filters, "notfeaturedmanufacturerid"), false); + var featuredManuIds = GetIdList(filters, "featuredmanufacturerid"); + var notFeaturedManuIds = GetIdList(filters, "notfeaturedmanufacturerid"); + if (featuredManuIds.Any()) + { + manufacturerId = manufacturerId == 0 ? featuredManuIds.First() : manufacturerId; + query = QueryManufacturers(query, featuredManuIds, true); + } + if (notFeaturedManuIds.Any()) + { + manufacturerId = manufacturerId == 0 ? notFeaturedManuIds.First() : manufacturerId; + query = QueryManufacturers(query, notFeaturedManuIds, false); + } var tagIds = GetIdList(filters, "tagid"); if (tagIds.Any()) { - query = + query = from p in query from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) select p; @@ -285,7 +219,7 @@ from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) var roleIds = GetIdList(filters, "roleid"); if (roleIds.Any()) { - query = + query = from p in query join acl in _aclRepository.Table on new { pid = p.Id, pname = "Product" } equals new { pid = acl.EntityId, pname = acl.EntityName } into pacl from acl in pacl.DefaultIfEmpty() @@ -338,7 +272,7 @@ from acl in pacl.DefaultIfEmpty() { if (rangeFilter != null && 1 == ((filter.Term as int?) ?? 0) && int.MaxValue == ((rangeFilter.UpperTerm as int?) ?? 0)) { - // has any category + // Has any category. query = query.Where(x => x.ProductCategories.Count > 0); } } @@ -346,7 +280,7 @@ from acl in pacl.DefaultIfEmpty() { if (rangeFilter != null && 1 == ((filter.Term as int?) ?? 0) && int.MaxValue == ((rangeFilter.UpperTerm as int?) ?? 0)) { - // has any manufacturer + // Has any manufacturer. query = query.Where(x => x.ProductManufacturers.Count > 0); } } @@ -525,7 +459,7 @@ from acl in pacl.DefaultIfEmpty() var storeId = (int)filter.Term; if (storeId != 0) { - query = + query = from p in query join sm in _storeMappingRepository.Table on new { pid = p.Id, pname = "Product" } equals new { pid = sm.EntityId, pname = sm.EntityName } into psm from sm in psm.DefaultIfEmpty() @@ -538,6 +472,7 @@ from sm in psm.DefaultIfEmpty() #endregion + // Grouping is very slow if there are many products. query = from p in query group p by p.Id into grp @@ -550,15 +485,13 @@ orderby grp.Key { if (sort.FieldName.IsEmpty()) { - // sort by relevance - if (categoryIds.Any()) + // Sort by relevance. + if (categoryId != 0) { - var categoryId = categoryIds.First(); query = OrderBy(ref ordered, query, x => x.ProductCategories.Where(pc => pc.CategoryId == categoryId).FirstOrDefault().DisplayOrder); } - else if (manufacturerIds.Any()) + else if (manufacturerId != 0) { - var manufacturerId = manufacturerIds.First(); query = OrderBy(ref ordered, query, x => x.ProductManufacturers.Where(pm => pm.ManufacturerId == manufacturerId).FirstOrDefault().DisplayOrder); } else if (FindFilter(searchQuery.Filters, "parentid") != null) @@ -615,7 +548,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 +566,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 +590,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 +615,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 +638,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.Published && !x.Deleted && !x.IsSystemProduct).Min(x => (double)x.Price); + var maxPrice = _productRepository.Table.Where(x => x.Published && !x.Deleted && !x.IsSystemProduct).Max(x => (double)x.Price); minPrice = FacetUtility.MakePriceEven(minPrice); maxPrice = FacetUtility.MakePriceEven(maxPrice); @@ -768,8 +687,6 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } - - #endregion } else if (kind == FacetGroupKind.Rating) { @@ -805,7 +722,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 +735,7 @@ protected virtual IDictionary GetFacets(CatalogSearchQuery s return result; } - #endregion + #endregion public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFlags loadFlags = ProductLoadFlags.None, bool direct = false) { @@ -841,9 +759,11 @@ public CatalogSearchResult Search(CatalogSearchQuery searchQuery, ProductLoadFla if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) { - query = query - .Skip(searchQuery.PageIndex * searchQuery.Take) - .Take(searchQuery.Take); + var skip = searchQuery.PageIndex * searchQuery.Take; + + query = query + .Skip(() => skip) + .Take(() => searchQuery.Take); var ids = query.Select(x => x.Id).ToArray(); hitsFactory = () => _productService.GetProductsByIds(ids, loadFlags); diff --git a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs similarity index 93% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs index 42562c44c6..6cfae38fd0 100644 --- a/src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryAliasMapper.cs +++ b/src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryAliasMapper.cs @@ -10,17 +10,18 @@ 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"; - private const string ALL_COMMONFACET_ALIAS_BY_KIND_KEY = "search.commonfacet.alias.kind.mappings.all"; + 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"; + private const string ALL_COMMONFACET_ALIAS_BY_KIND_KEY = "search:commonfacet.alias.kind.mappings.all"; - private const string ALL_VARIANT_ID_BY_ALIAS_KEY = "search.variant.id.alias.mappings.all"; - private const string ALL_VARIANT_ALIAS_BY_ID_KEY = "search.variant.alias.id.mappings.all"; + private const string ALL_VARIANT_ID_BY_ALIAS_KEY = "search:variant.id.alias.mappings.all"; + private const string ALL_VARIANT_ALIAS_BY_ID_KEY = "search:variant.alias.id.mappings.all"; private readonly ICacheManager _cacheManager; private readonly IRepository _localizedPropertyRepository; @@ -66,17 +67,17 @@ 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 - .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias" && x.LocaleValue != null && x.LocaleValue != string.Empty) - .ToList() - .ForEach(caching); + var properties = _localizedPropertyRepository.TableUntracked + .Where(x => x.LocaleKeyGroup == localeKeyGroup && x.LocaleKey == "Alias") + .ToList(); + + // SQL CE: leads to an error when checked in the query. + properties + .Where(x => !string.IsNullOrWhiteSpace(x.LocaleValue)) + .ToList() + .ForEach(caching); } #region Specification Attributes @@ -297,8 +298,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 +465,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 +487,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 83% rename from src/Libraries/SmartStore.Services/Search/Modelling/CatalogSearchQueryFactory.cs rename to src/Libraries/SmartStore.Services/Search/Catalog/Modelling/CatalogSearchQueryFactory.cs index 808fd62679..3c30544a0d 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; @@ -337,13 +330,15 @@ private void AddFacet( throw new SmartException($"Unknown field name for facet group '{kind.ToString()}'"); } - var descriptor = new FacetDescriptor(fieldName); - descriptor.Label = _services.Localization.GetResource(FacetDescriptor.GetLabelResourceKey(kind) ?? kind.ToString()); - descriptor.IsMultiSelect = isMultiSelect; - descriptor.DisplayOrder = displayOrder; - descriptor.OrderBy = sorting; - descriptor.MinHitCount = _searchSettings.FilterMinHitCount; - descriptor.MaxChoicesCount = _searchSettings.FilterMaxChoicesCount; + var descriptor = new FacetDescriptor(fieldName) + { + Label = _services.Localization.GetResource(FacetUtility.GetLabelResourceKey(kind) ?? kind.ToString()), + IsMultiSelect = isMultiSelect, + DisplayOrder = displayOrder, + OrderBy = sorting, + MinHitCount = _searchSettings.FilterMinHitCount, + MaxChoicesCount = _searchSettings.FilterMaxChoicesCount + }; addValues(descriptor); query.WithFacet(descriptor); @@ -481,27 +476,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 +593,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) + protected virtual bool GetValueFor(CatalogSearchQuery query, string key, FacetGroupKind kind, 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) - { - 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; - } + return TryGetValueFor(_catalogSearchQueryAliasMapper.GetCommonFacetAliasByGroupKind(kind, query.LanguageId ?? 0) ?? key, out value); } } } 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..9fd25c0d3a --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/ForumSearchQuery.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Search; +using SmartStore.Services.Customers; + +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 ForumSearchQuery VisibleOnly(Customer customer, bool includeCustomerRoles) + { + if (customer != null) + { + if (!customer.IsForumModerator()) + { + // See also LinqForumSearchService. + var publishedCombined = SearchFilter.Combined( + SearchFilter.ByField("published", true).ExactMatch().NotAnalyzed(), + SearchFilter.ByField("customerid", customer.Id).ExactMatch().NotAnalyzed()); + + WithFilter(publishedCombined); + } + + if (includeCustomerRoles) + { + var allowedRoleIds = customer.CustomerRoles.Where(x => x.Active).Select(x => x.Id).ToArray(); + if (allowedRoleIds != null && allowedRoleIds.Length > 0) + { + var roleIds = allowedRoleIds.Where(x => x != 0).Distinct().ToList(); + if (roleIds.Any()) + { + roleIds.Insert(0, 0); + WithFilter(SearchFilter.Combined(roleIds.Select(x => SearchFilter.ByField("roleid", x).ExactMatch().NotAnalyzed()).ToArray())); + } + } + } + } + + return this; + } + + public ForumSearchQuery PublishedOnly(bool value) + { + return WithFilter(SearchFilter.ByField("published", value).Mandatory().ExactMatch().NotAnalyzed()); + } + + 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..cce742613e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/LinqForumSearchService.cs @@ -0,0 +1,437 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Data.Entity; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Security; +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 _forumTopicRepository; + private readonly IRepository _forumRepository; + private readonly IRepository _forumGroupRepository; + private readonly IRepository _storeMappingRepository; + private readonly IRepository _aclRepository; + private readonly IForumService _forumService; + private readonly ICommonServices _services; + private readonly CustomerSettings _customerSettings; + + public LinqForumSearchService( + IRepository forumPostRepository, + IRepository forumTopicRepository, + IRepository forumRepository, + IRepository forumGroupRepository, + IRepository storeMappingRepository, + IRepository aclRepository, + IForumService forumService, + ICommonServices services, + CustomerSettings customerSettings) + { + _forumPostRepository = forumPostRepository; + _forumTopicRepository = forumTopicRepository; + _forumRepository = forumRepository; + _forumGroupRepository = forumGroupRepository; + _storeMappingRepository = storeMappingRepository; + _aclRepository = aclRepository; + _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 customer = _services.WorkContext.CurrentCustomer; + 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)) + )); + } + } + + // Flatten filters. + foreach (var filter in searchQuery.Filters) + { + var combinedFilter = filter as ICombinedSearchFilter; + if (combinedFilter != null) + { + // Find VisibleOnly combined filter and process it separately. + var cf = combinedFilter.Filters.OfType().ToArray(); + if (cf.Length == 2 && cf[0].FieldName == "published" && true == (bool)cf[0].Term && cf[1].FieldName == "customerid") + { + if (!customer.IsForumModerator()) + { + query = query.Where(x => x.ForumTopic.Published && (x.Published || x.CustomerId == customer.Id)); + } + } + else + { + FlattenFilters(combinedFilter.Filters, filters); + } + } + else + { + filters.Add(filter); + } + } + + if (!QuerySettings.IgnoreAcl) + { + var roleIds = GetIdList(filters, "roleid"); + if (roleIds.Any()) + { + query = + from fp in query + join ft in _forumTopicRepository.TableUntracked on fp.TopicId equals ft.Id + join ff in _forumRepository.Table on ft.ForumId equals ff.Id + join fg in _forumGroupRepository.Table on ff.ForumGroupId equals fg.Id + join a in _aclRepository.Table on new { a1 = fg.Id, a2 = "ForumGroup" } equals new { a1 = a.EntityId, a2 = a.EntityName } into fg_acl + from a in fg_acl.DefaultIfEmpty() + where !fg.SubjectToAcl || roleIds.Contains(a.CustomerRoleId) + select fp; + } + } + + 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 == "published") + { + query = query.Where(x => x.Published == (bool)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 take = maxChoices * 3; + var customers = customerQuery.Take(() => take).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)) + { + var skip = searchQuery.PageIndex * searchQuery.Take; + query = query + .Skip(() => skip) + .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..d4c9c754d5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/Forum/Modelling/ForumSearchQueryFactory.cs @@ -0,0 +1,301 @@ +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); + + // Visibility. + query.VisibleOnly(_services.WorkContext.CurrentCustomer, !QuerySettings.IgnoreAcl); + + // 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 TryGetValueFor(_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..0fa710c25d --- /dev/null +++ b/src/Libraries/SmartStore.Services/Search/SearchQueryFactoryBase.cs @@ -0,0 +1,102 @@ +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) + { + return TryGetValueFor(key, out T value) ? value : default; + } + + protected virtual bool TryGetValueFor(string key, out T value) + { + var strValue = _httpContext.Request?.Unvalidated.Form?[key] ?? _httpContext.Request?.Unvalidated.QueryString?[key]; + + if (strValue.HasValue()) + { + return CommonHelper.TryConvert(strValue, out value); + } + + value = default; + 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..2b77fadb24 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) { @@ -67,7 +73,7 @@ public IList GetAclRecords(T entity) where T : BaseEntity, IAclSup Guard.NotNull(entity, nameof(entity)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); return GetAclRecordsFor(entityName, entityId); } @@ -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 { @@ -103,7 +131,7 @@ public virtual void InsertAclRecord(T entity, int customerRoleId) where T : B throw new ArgumentOutOfRangeException(nameof(customerRoleId)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); var aclRecord = new AclRecord { @@ -121,61 +149,123 @@ 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) + public virtual int[] GetCustomerRoleIdsWithAccessTo(string entityName, int entityId) { Guard.NotEmpty(entityName, nameof(entityName)); 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?.CustomerRoles); } - public virtual bool Authorize(string entityName, int entityId, Customer customer) + public virtual bool Authorize(string entityName, int entityId, IEnumerable roles) { Guard.NotEmpty(entityName, nameof(entityName)); if (entityId <= 0) return false; - if (customer == null) - return false; - - if (QuerySettings.IgnoreAcl) + if (!HasActiveAcl) return true; - foreach (var role1 in customer.CustomerRoles.Where(cr => cr.Active)) + if (roles == null) + return false; + + foreach (var role in roles) { - foreach (var role2Id in GetCustomerRoleIdsWithAccess(entityName, entityId)) + if (!role.Active) + continue; + + foreach (var role2Id in GetCustomerRoleIdsWithAccessTo(entityName, entityId)) { - if (role1.Id == role2Id) + if (role.Id == role2Id) { - // yes, we have such permission + // Yes, we have such permission return true; } } } - // no permission granted + // 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..5130dcc09f 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 @@ -72,7 +80,7 @@ public partial interface IAclService /// Entity name to check permission for /// Entity id to check permission for /// Customer role identifiers - int[] GetCustomerRoleIdsWithAccess(string entityName, int entityId); + int[] GetCustomerRoleIdsWithAccessTo(string entityName, int entityId); /// /// Authorize ACL permission @@ -83,15 +91,24 @@ public partial interface IAclService /// true - authorized; otherwise, false bool Authorize(string entityName, int entityId); + ///// + ///// Authorize ACL permission + ///// + ///// Type + ///// Entity name to check permission for + ///// Entity id to check permission for + ///// Customer + ///// true - authorized; otherwise, false + //bool Authorize(string entityName, int entityId, Customer customer); + /// /// Authorize ACL permission /// - /// Type /// Entity name to check permission for /// Entity id to check permission for - /// Customer + /// Roles to check access permission for. Inactive roles will be skipped. /// true - authorized; otherwise, false - bool Authorize(string entityName, int entityId, Customer customer); + bool Authorize(string entityName, int entityId, IEnumerable roles); } public static class IAclServiceExtensions @@ -102,12 +119,17 @@ public static class IAclServiceExtensions /// Type /// Entity /// Customer role identifiers - public static int[] GetCustomerRoleIdsWithAccess(this IAclService aclService, T entity) where T : BaseEntity, IAclSupported + public static int[] GetCustomerRoleIdsWithAccessTo(this IAclService aclService, T entity) where T : BaseEntity, IAclSupported { if (entity == null) return new int[0]; - return aclService.GetCustomerRoleIdsWithAccess(typeof(T).Name, entity.Id); + return aclService.GetCustomerRoleIdsWithAccessTo(entity.GetEntityName(), entity.Id); + } + + public static bool Authorize(this IAclService aclService, string entityName, int entityId, Customer customer) + { + return aclService.Authorize(entityName, entityId, customer?.CustomerRoles); } /// @@ -124,7 +146,7 @@ public static bool Authorize(this IAclService aclService, T entity) where T : if (!entity.SubjectToAcl) return true; - return aclService.Authorize(typeof(T).Name, entity.Id); + return aclService.Authorize(entity.GetEntityName(), entity.Id); } /// @@ -142,7 +164,25 @@ public static bool Authorize(this IAclService aclService, T entity, Customer if (!entity.SubjectToAcl) return true; - return aclService.Authorize(typeof(T).Name, entity.Id, customer); + return aclService.Authorize(entity.GetEntityName(), entity.Id, customer?.CustomerRoles); + } + + /// + /// Authorize ACL permission + /// + /// Type + /// Entity + /// Roles to check access permission for. Inactive roles will be skipped. + /// true - authorized; otherwise, false + public static bool Authorize(this IAclService aclService, T entity, IEnumerable roles) where T : BaseEntity, IAclSupported + { + if (entity == null) + return false; + + if (!entity.SubjectToAcl) + return true; + + return aclService.Authorize(entity.GetEntityName(), entity.Id, roles); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Security/PermissionService.cs b/src/Libraries/SmartStore.Services/Security/PermissionService.cs index 16d0fa0ac0..dec98fd0fe 100644 --- a/src/Libraries/SmartStore.Services/Security/PermissionService.cs +++ b/src/Libraries/SmartStore.Services/Security/PermissionService.cs @@ -10,12 +10,11 @@ namespace SmartStore.Services.Security { - /// - /// Permission service - /// - public partial class PermissionService : IPermissionService + /// + /// Permission service + /// + public partial class PermissionService : IPermissionService { - #region Constants /// /// Cache key for storing a valie indicating whether a certain customer role has a permission /// @@ -25,20 +24,12 @@ public partial class PermissionService : IPermissionService /// private const string PERMISSIONS_ALLOWED_KEY = "permission:allowed-{0}-{1}"; private const string PERMISSIONS_PATTERN_KEY = "permission:*"; - #endregion - - #region Fields private readonly IRepository _permissionRecordRepository; - private readonly IRepository _customerRoleRepository; private readonly ICustomerService _customerService; private readonly IWorkContext _workContext; private readonly ICacheManager _cacheManager; - #endregion - - #region Ctor - /// /// Ctor /// @@ -48,19 +39,16 @@ public partial class PermissionService : IPermissionService /// Cache manager public PermissionService( IRepository permissionRecordRepository, - IRepository customerRoleRepository, ICustomerService customerService, - IWorkContext workContext, ICacheManager cacheManager) + IWorkContext workContext, + ICacheManager cacheManager) { - this._permissionRecordRepository = permissionRecordRepository; - this._customerRoleRepository = customerRoleRepository; - this._customerService = customerService; - this._workContext = workContext; - this._cacheManager = cacheManager; + _permissionRecordRepository = permissionRecordRepository; + _customerService = customerService; + _workContext = workContext; + _cacheManager = cacheManager; } - #endregion - #region Utilities /// @@ -71,15 +59,21 @@ public PermissionService( /// true - authorized; otherwise, false protected virtual bool Authorize(string permissionRecordSystemName, CustomerRole customerRole) { - if (String.IsNullOrEmpty(permissionRecordSystemName)) + if (string.IsNullOrEmpty(permissionRecordSystemName)) + { return false; + } - string key = string.Format(PERMISSIONS_ALLOWED_KEY, customerRole.Id, permissionRecordSystemName); + var key = string.Format(PERMISSIONS_ALLOWED_KEY, customerRole.Id, permissionRecordSystemName); return _cacheManager.Get(key, () => { foreach (var permission1 in customerRole.PermissionRecords) + { if (permission1.SystemName.Equals(permissionRecordSystemName, StringComparison.InvariantCultureIgnoreCase)) + { return true; + } + } return false; }); @@ -311,16 +305,20 @@ public virtual bool Authorize(string permissionRecordSystemName) /// true - authorized; otherwise, false public virtual bool Authorize(string permissionRecordSystemName, Customer customer) { - if (String.IsNullOrEmpty(permissionRecordSystemName)) + if (string.IsNullOrEmpty(permissionRecordSystemName)) + { return false; + } var customerRoles = customer.CustomerRoles.Where(cr => cr.Active); foreach (var role in customerRoles) + { if (Authorize(permissionRecordSystemName, role)) - //yes, we have such permission + { return true; + } + } - //no permission found return false; } diff --git a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs index 499e977c7f..32ba800597 100644 --- a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs +++ b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs @@ -24,6 +24,7 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord ManageBlog = new PermissionRecord { Name = "Admin area. Manage Blog", SystemName = "ManageBlog", Category = "Content Management" }; public static readonly PermissionRecord ManageWidgets = new PermissionRecord { Name = "Admin area. Manage Widgets", SystemName = "ManageWidgets", Category = "Content Management" }; public static readonly PermissionRecord ManageTopics = new PermissionRecord { Name = "Admin area. Manage Topics", SystemName = "ManageTopics", Category = "Content Management" }; + public static readonly PermissionRecord ManageMenus = new PermissionRecord { Name = "Admin area. Manage Menus", SystemName = "ManageMenus", Category = "Content Management" }; public static readonly PermissionRecord ManageForums = new PermissionRecord { Name = "Admin area. Manage Forums", SystemName = "ManageForums", Category = "Content Management" }; public static readonly PermissionRecord ManageMessageTemplates = new PermissionRecord { Name = "Admin area. Manage Message Templates", SystemName = "ManageMessageTemplates", Category = "Content Management" }; public static readonly PermissionRecord ManageCountries = new PermissionRecord { Name = "Admin area. Manage Countries", SystemName = "ManageCountries", Category = "Configuration" }; @@ -79,6 +80,7 @@ public virtual IEnumerable GetPermissions() ManageBlog, ManageWidgets, ManageTopics, + ManageMenus, ManageForums, ManageMessageTemplates, ManageCountries, @@ -138,6 +140,7 @@ public virtual IEnumerable GetDefaultPermissions() ManageBlog, ManageWidgets, ManageTopics, + ManageMenus, ManageForums, ManageMessageTemplates, ManageCountries, diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index 15d0d8bb35..639dc8efc1 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -63,6 +63,39 @@ public partial interface IUrlRecordService : IScopedService /// Customer collection IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive); + /// + /// Prefetches a collection of url records properties for a range of entities in one go + /// and caches them for the duration of the current request. + /// + /// Entity name + /// + /// The entity ids to prefetch url records for. Can be null, + /// in which case all records for the requested entity name are loaded. + /// + /// Whether represents a range of ids (perf). + /// Whether is already sorted (perf). + /// Url record collection + /// + /// Be careful not to load large amounts of data at once (e.g. for "Product" scope with large range). + /// + void PrefetchUrlRecords(string entityName, int[] languageIds, int[] entityIds, bool isRange = false, bool isSorted = false); + + /// + /// Prefetches a collection of url records properties for a range of entities in one go. + /// + /// Entity name + /// + /// The entity ids to prefetch url records for. Can be null, + /// in which case all records for the requested entity name are loaded. + /// + /// Whether represents a range of ids (perf). + /// Whether is already sorted (perf). + /// Url record collection + /// + /// Be careful not to load large amounts of data at once (e.g. for "Product" scope with large range). + /// + UrlRecordCollection GetUrlRecordCollection(string entityName, int[] languageIds, int[] entityIds, bool isRange = false, bool isSorted = false); + /// /// Gets all URL records for the specified entity /// diff --git a/src/Libraries/SmartStore.Services/Seo/IXmlSitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/IXmlSitemapGenerator.cs index 0861c8fe92..5e833a086e 100644 --- a/src/Libraries/SmartStore.Services/Seo/IXmlSitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/IXmlSitemapGenerator.cs @@ -1,4 +1,7 @@ +using SmartStore.Services.Tasks; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace SmartStore.Services.Seo { @@ -8,24 +11,41 @@ namespace SmartStore.Services.Seo public partial interface IXmlSitemapGenerator { /// - /// Gets the sitemap XML + /// Gets the sitemap partition /// /// - /// The page index. 0 or null retrieves the first document, which can be a sitemap INDEX document + /// The page index. 0 retrieves the first document, which can be a sitemap INDEX document /// (when the sitemap size exceeded the limits). An index greater 0 retrieves the /// sitemap XML document at this index, but only when the sitemap is actually indexed (otherwise null is returned) /// - /// Sitemap XML - string GetSitemap(int? index = null); + /// Sitemap partition + Task GetSitemapPartAsync(int index = 0); /// - /// Removes the sitemap from the cache for a rebuild. + /// Rebuilds the collection of XML sitemap documents for a store/language combination. If there are less than 1.000 sitemap + /// nodes, only one sitemap document will exist in the collection, otherwise a sitemap index document will be + /// the first entry in the collection and all other entries will be sitemap XML documents. + /// + /// The build context + /// + /// During rebuilding, requests are being served from the existing cache. + /// Once rebuild is completed, the cache is updated. + /// + Task RebuildAsync(XmlSitemapBuildContext ctx); + + /// + /// Determines whether a rebuild is already running. /// - void Invalidate(); + bool IsRebuilding(int storeId, int languageId); /// /// Indicates whether the sitemap has been generated and cached. /// - bool IsGenerated { get; } + bool IsGenerated(int storeId, int languageId); + + /// + /// Removes the sitemap from the cache for a rebuild. + /// + void Invalidate(int storeId, int languageId); } } diff --git a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs index 93b8e87f68..3a2c115b85 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 @@ -147,7 +131,7 @@ public static string GetSeName(this T entity) Guard.NotNull(entity, nameof(entity)); return GetSeName( - typeof(T).Name, + entity.GetEntityName(), entity.Id, EngineContext.Current.Resolve().WorkingLanguage.Id, EngineContext.Current.Resolve(), @@ -172,7 +156,7 @@ public static string GetSeName(this T entity, Guard.NotNull(entity, nameof(entity)); return GetSeName( - typeof(T).Name, + entity.GetEntityName(), entity.Id, languageId, EngineContext.Current.Resolve(), @@ -201,7 +185,7 @@ public static string GetSeName(this T entity, Guard.NotNull(entity, nameof(entity)); return GetSeName( - typeof(T).Name, + entity.GetEntityName(), entity.Id, languageId, urlRecordService, @@ -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)) @@ -316,16 +298,14 @@ public static string ValidateSeName(this T entity, } // ensure this sename is not reserved yet - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); 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) @@ -368,7 +348,8 @@ public static string GetSeName(string name, SeoSettings seoSettings) name, seoSettings == null ? false : seoSettings.ConvertNonWesternChars, seoSettings == null ? false : seoSettings.AllowUnicodeCharsInUrls, - seoSettings == null ? null : seoSettings.SeoNameCharConversion); + true, + seoSettings?.SeoNameCharConversion); } #endregion diff --git a/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs b/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs index ff719bbe05..5e07f2a9fb 100644 --- a/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs +++ b/src/Libraries/SmartStore.Services/Seo/Tasks/RebuildXmlSitemapTask.cs @@ -4,30 +4,51 @@ using System.Text; using System.Threading.Tasks; using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Localization; +using SmartStore.Services.Stores; using SmartStore.Services.Tasks; namespace SmartStore.Services.Seo { - public class RebuildXmlSitemapTask : ITask + public class RebuildXmlSitemapTask : AsyncTask { + private readonly IStoreService _storeService; + private readonly ILanguageService _languageService; private readonly IXmlSitemapGenerator _generator; private readonly SeoSettings _seoSettings; - public RebuildXmlSitemapTask(IXmlSitemapGenerator generator, SeoSettings seoSettings) + public RebuildXmlSitemapTask( + IStoreService storeService, + ILanguageService languageService, + IXmlSitemapGenerator generator, + SeoSettings seoSettings) { + _storeService = storeService; + _languageService = languageService; _generator = generator; _seoSettings = seoSettings; } - public void Execute(TaskExecutionContext ctx) + public override async Task ExecuteAsync(TaskExecutionContext ctx) { - if (_generator.IsGenerated) + var stores = _storeService.GetAllStores(); + + foreach (var store in stores) { - _generator.Invalidate(); + var languages = _languageService.GetAllLanguages(false, store.Id); + var buildContext = new XmlSitemapBuildContext(store, languages.ToArray()) + { + CancellationToken = ctx.CancellationToken, + ProgressCallback = OnProgress + }; + + await _generator.RebuildAsync(buildContext); } - // enforces refresh - _generator.GetSitemap(0); + void OnProgress(int value, int max, string msg) + { + ctx.SetProgress(value, max, msg, true); + } } } } diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index aacd0eb835..df8bad3de9 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Threading; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Seo; namespace SmartStore.Services.Seo @@ -14,22 +16,58 @@ public partial class UrlRecordService : ScopedServiceBase, IUrlRecordService /// /// 0 = segment (EntityName.IdRange), 1 = language id /// - 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_SEGMENT_KEY = "urlrecord:segment:{0}-lang-{1}"; + const string URLRECORD_SEGMENT_PATTERN = "urlrecord:segment:{0}*"; + const string URLRECORD_ALL_PATTERN = "urlrecord:*"; const string URLRECORD_ALL_ACTIVESLUGS_KEY = "urlrecord:all-active-slugs"; private readonly IRepository _urlRecordRepository; private readonly ICacheManager _cacheManager; private readonly SeoSettings _seoSettings; + private readonly PerformanceSettings _performanceSettings; - public UrlRecordService(ICacheManager cacheManager, IRepository urlRecordRepository, SeoSettings seoSettings) + private readonly IDictionary _prefetchedCollections; + private static int _lastCacheSegmentSize = -1; + + public UrlRecordService( + ICacheManager cacheManager, + IRepository urlRecordRepository, + SeoSettings seoSettings, + PerformanceSettings performanceSettings) { - this._cacheManager = cacheManager; - this._urlRecordRepository = urlRecordRepository; - this._seoSettings = seoSettings; + _cacheManager = cacheManager; + _urlRecordRepository = urlRecordRepository; + _seoSettings = seoSettings; + _performanceSettings = performanceSettings; + + _prefetchedCollections = new Dictionary(StringComparer.OrdinalIgnoreCase); + + ValidateCacheState(); } + private void ValidateCacheState() + { + // Ensure that after a segment size change the cache segments are invalidated. + var size = _performanceSettings.CacheSegmentSize; + var changed = _lastCacheSegmentSize == -1; + + if (size <= 0) + { + _performanceSettings.CacheSegmentSize = size = 1; + } + + if (_lastCacheSegmentSize > 0 && _lastCacheSegmentSize != size) + { + _cacheManager.RemoveByPattern(URLRECORD_SEGMENT_PATTERN); + changed = true; + } + + if (changed) + { + Interlocked.Exchange(ref _lastCacheSegmentSize, size); + } + } + protected override void OnClearCache() { _cacheManager.Remove(URLRECORD_ALL_PATTERN); @@ -42,7 +80,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 +121,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 +137,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 { } } @@ -146,6 +184,83 @@ public virtual IList GetUrlRecordsFor(string entityName, int entityId return query.ToList(); } + public virtual void PrefetchUrlRecords(string entityName, int[] languageIds, int[] entityIds, bool isRange = false, bool isSorted = false) + { + var collection = GetUrlRecordCollectionInternal(entityName, languageIds, entityIds, isRange, isSorted); + + if (_prefetchedCollections.TryGetValue(entityName, out var existing)) + { + collection.MergeWith(existing); + } + else + { + _prefetchedCollections[entityName] = collection; + } + } + + public virtual UrlRecordCollection GetUrlRecordCollection(string entityName, int[] languageIds, int[] entityIds, bool isRange = false, bool isSorted = false) + { + return GetUrlRecordCollectionInternal(entityName, languageIds, entityIds, isRange, isSorted); + } + + public virtual UrlRecordCollection GetUrlRecordCollectionInternal(string entityName, int[] languageIds, int[] entityIds, bool isRange = false, bool isSorted = false) + { + Guard.NotEmpty(entityName, nameof(entityName)); + + using (new DbContextScope(proxyCreation: false, lazyLoading: false)) + { + var query = from x in _urlRecordRepository.TableUntracked + where x.EntityName == entityName && x.IsActive + select x; + + var requestedSet = entityIds; + + if (entityIds != null && entityIds.Length > 0) + { + if (isRange) + { + if (!isSorted) + { + Array.Sort(entityIds); + } + + var min = entityIds[0]; + var max = entityIds[entityIds.Length - 1]; + + if (entityIds.Length == 2 && max > min + 1) + { + // Only min & max were passed, create the range sequence. + requestedSet = Enumerable.Range(min, max - min + 1).ToArray(); + } + + query = query.Where(x => x.EntityId >= min && x.EntityId <= max); + } + else + { + requestedSet = entityIds; + query = query.Where(x => entityIds.Contains(x.EntityId)); + } + } + + if (languageIds != null && languageIds.Length > 0) + { + if (languageIds.Length == 1) + { + // Avoid "The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities". + var languageId = languageIds[0]; + query = query.Where(x => x.LanguageId == languageId); + } + else + { + query = query.Where(x => languageIds.Contains(x.LanguageId)); + } + } + + // Don't sort DESC, because latter items overwrite exisiting ones (it's the same as sorting DESC and taking the first) + return new UrlRecordCollection(entityName, requestedSet, query.OrderBy(x => x.Id).ToList()); + } + } + public virtual UrlRecord GetBySlug(string slug) { // INFO: (mc) Caching unnecessary here. This is not a 'bottleneck' function. @@ -155,12 +270,22 @@ 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; } public virtual string GetActiveSlug(int entityId, string entityName, int languageId) { + if (_prefetchedCollections.TryGetValue(entityName, out var collection)) + { + var cachedItem = collection.Find(languageId, entityId); + if (cachedItem != null) + { + return cachedItem.Slug.EmptyNull(); + } + } + if (IsInScope) { return GetActiveSlugUncached(entityId, entityName, languageId); @@ -172,18 +297,21 @@ public virtual string GetActiveSlug(int entityId, string entityName, int languag { var allActiveSlugs = _cacheManager.Get(URLRECORD_ALL_ACTIVESLUGS_KEY, () => { - var query = from x in _urlRecordRepository.TableUntracked - where x.IsActive - orderby x.Id descending - select x; - - var result = query.ToDictionarySafe( - x => GenerateKey(x.EntityId, x.EntityName, x.LanguageId), - x => x.Slug, - StringComparer.OrdinalIgnoreCase); - - return result; - }); + using (new DbContextScope(proxyCreation: false, lazyLoading: false)) + { + var query = from x in _urlRecordRepository.TableUntracked + where x.IsActive + orderby x.Id descending + select x; + + var result = query.ToDictionarySafe( + x => GenerateKey(x.EntityId, x.EntityName, x.LanguageId), + x => x.Slug, + StringComparer.OrdinalIgnoreCase); + + return result; + } + }, independent: true); var key = GenerateKey(entityId, entityName, languageId); if (!allActiveSlugs.TryGetValue(key, out slug)) @@ -193,7 +321,7 @@ orderby x.Id descending } else { - var slugs = GetCachedSlugsSegment(entityName, entityId, languageId); + var slugs = GetCacheSegment(entityName, entityId, languageId); if (!slugs.TryGetValue(entityId, out slug)) { @@ -223,7 +351,7 @@ public virtual UrlRecord SaveSlug(T entity, string slug, int languageId) wher Guard.NotNull(entity, nameof(entity)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); UrlRecord result = null; var query = from ur in _urlRecordRepository.Table @@ -379,50 +507,50 @@ 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, () => { - var query = from ur in _urlRecordRepository.TableUntracked - where - ur.EntityId >= minEntityId && - ur.EntityId <= maxEntityId && - ur.EntityName == entityName && - ur.LanguageId == languageId && - ur.IsActive - orderby ur.Id descending - select ur; - - var urlRecords = query.ToList(); - - var dict = new Dictionary(urlRecords.Count); - - foreach (var ur in urlRecords) + using (new DbContextScope(proxyCreation: false, lazyLoading: false)) { - dict[ur.EntityId] = ur.Slug.EmptyNull(); + var query = from ur in _urlRecordRepository.TableUntracked + where + ur.EntityId >= minEntityId && + ur.EntityId <= maxEntityId && + ur.EntityName == entityName && + ur.LanguageId == languageId && + ur.IsActive + orderby ur.Id descending + select ur; + + var urlRecords = query.ToList(); + + var dict = new Dictionary(urlRecords.Count); + + foreach (var ur in urlRecords) + { + dict[ur.EntityId] = ur.Slug.EmptyNull(); + } + + return dict; } - - return dict; - }); + }, independent: true); } /// /// 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 +570,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(_performanceSettings.CacheSegmentSize, out minId); + return (entityName + "." + maxId.ToString()).ToLowerInvariant(); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Seo/XmlSitemapBuildContext.cs b/src/Libraries/SmartStore.Services/Seo/XmlSitemapBuildContext.cs new file mode 100644 index 0000000000..6aa8504f00 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapBuildContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.Tasks; + +namespace SmartStore.Services.Seo +{ + public class XmlSitemapBuildContext + { + public XmlSitemapBuildContext(Store store, Language[] languages) + { + Guard.NotNull(store, nameof(store)); + Guard.NotEmpty(languages, nameof(languages)); + + Store = store; + Languages = languages; + Protocol = Store.ForceSslForAllPages ? "https" : "http"; + } + + public CancellationToken CancellationToken { get; set; } + public ProgressCallback ProgressCallback { get; set; } + public Store Store { get; set; } + public Language[] Languages { get; set; } + + public string Protocol { get; private set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs index 5950c9e11c..17a8e5356e 100644 --- a/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapGenerator.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading; +using System.Data.Entity; using System.Web.Mvc; using System.Xml.Linq; using SmartStore.Core; @@ -10,30 +12,29 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Topics; using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.Localization; using SmartStore.Services.Search; using SmartStore.Services.Topics; +using SmartStore.Core.IO; +using SmartStore.Utilities; +using System.IO; +using SmartStore.Collections; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Localization; +using System.Threading.Tasks; namespace SmartStore.Services.Seo { public partial class XmlSitemapGenerator : IXmlSitemapGenerator { - /// - /// Key for seo sitemap - /// - /// - /// {0} : sitemap index - /// {1} : current store id - /// {2} : current language id - /// - public const string XMLSITEMAP_DOCUMENT_KEY = "sitemap:xml-idx{0}-{1}-{2}"; - public const string XMLSITEMAP_PATTERN_KEY = "sitemap:xml*"; - private const string SiteMapsNamespace = "http://www.sitemaps.org/schemas/sitemap/0.9"; private const string XhtmlNamespace = "http://www.w3.org/1999/xhtml"; + private const string SiteMapFileNamePattern = "sitemap-{0}.xml"; + private const string LockFileNamePattern = "sitemap-{0}-{1}.lock"; /// /// The maximum number of sitemaps a sitemap index file can contain. @@ -43,27 +44,30 @@ public partial class XmlSitemapGenerator : IXmlSitemapGenerator /// /// The maximum number of sitemap nodes allowed in a sitemap file. The absolute maximum allowed is 50,000 /// according to the specification. See http://www.sitemaps.org/protocol.html but the file size must also be - /// less than 10MB. After some experimentation, a maximum of 1.000 nodes keeps the file size below 10MB. + /// less than 10MB. After some experimentation, a maximum of 2.000 nodes keeps the file size below 10MB. /// - private const int MaximumSiteMapNodeCount = 1000; + private const int MaximumSiteMapNodeCount = 2000; /// /// The maximum size of a sitemap file in bytes (10MB). /// private const int MaximumSiteMapSizeInBytes = 10485760; - private readonly ICategoryService _categoryService; + private readonly ICategoryService _categoryService; private readonly IProductService _productService; private readonly IManufacturerService _manufacturerService; private readonly ITopicService _topicService; private readonly ILanguageService _languageService; private readonly ICustomerService _customerService; private readonly ICatalogSearchService _catalogSearchService; - private readonly SeoSettings _seoSettings; - private readonly SecuritySettings _securitySettings; + private readonly IUrlRecordService _urlRecordService; private readonly ICommonServices _services; + private readonly ILockFileManager _lockFileManager; private readonly UrlHelper _urlHelper; + private readonly IVirtualFolder _tenantFolder; + private readonly string _baseDir; + public XmlSitemapGenerator( ICategoryService categoryService, IProductService productService, @@ -72,9 +76,9 @@ public XmlSitemapGenerator( ILanguageService languageService, ICustomerService customerService, ICatalogSearchService catalogSearchService, - SeoSettings commonSettings, - SecuritySettings securitySettings, + IUrlRecordService urlRecordService, ICommonServices services, + ILockFileManager lockFileManager, UrlHelper urlHelper) { _categoryService = categoryService; @@ -84,139 +88,484 @@ public XmlSitemapGenerator( _languageService = languageService; _customerService = customerService; _catalogSearchService = catalogSearchService; - _seoSettings = commonSettings; - _securitySettings = securitySettings; + _urlRecordService = urlRecordService; _services = services; + _lockFileManager = lockFileManager; _urlHelper = urlHelper; + _tenantFolder = _services.ApplicationEnvironment.TenantFolder; + _baseDir = _tenantFolder.Combine("Sitemaps"); + Logger = NullLogger.Instance; - } + + } - public ILogger Logger + public ILogger Logger { get; set; } + + public virtual async Task GetSitemapPartAsync(int index = 0) { - get; - set; + return await GetSitemapPartAsync(index, false); } - public void Invalidate() + private async Task GetSitemapPartAsync(int index, bool isRetry) { - _services.Cache.RemoveByPattern(XMLSITEMAP_PATTERN_KEY); + Guard.NotNegative(index, nameof(index)); + + var store = _services.StoreContext.CurrentStore; + var language = _services.WorkContext.WorkingLanguage; + + var exists = SitemapFileExists(store.Id, language.Id, index, out var path, out var name); + + if (exists) + { + return new XmlSitemapPartition + { + Index = index, + Name = name, + LanguageId = language.Id, + StoreId = store.Id, + ModifiedOnUtc = _tenantFolder.GetFileLastWriteTimeUtc(path), + Stream = _tenantFolder.OpenFile(path) + }; + } + + if (isRetry) + { + var msg = "Could not generate XML sitemap. Index: {0}, Date: {1}".FormatInvariant(index, DateTime.UtcNow); + Logger.Error(msg); + throw new SmartException(msg); + } + + if (index > 0) + { + // File with index greater 0 has been requested, but it does not exist. + // Now we have to determine whether just the passed index is out of range + // or the files has never been created before. + // If the main file (index 0) exists, the action should return NotFoundResult, + // otherwise the rebuild process should be started or waited for. + + if (SitemapFileExists(store.Id, language.Id, 0, out path, out name)) + { + throw new IndexOutOfRangeException("The sitemap file '{0}' does not exist.".FormatInvariant(name)); + } + } + + // The main sitemap document with index 0 does not exist, meaning: the whole sitemap + // needs to be created and cached by partitions. + + var wasRebuilding = false; + var lockFilePath = GetLockFilePath(store.Id, language.Id); + + while (IsRebuilding(lockFilePath)) + { + // The rebuild process is already running, either started + // by the task scheduler or another HTTP request. + // We should wait for completion. + + wasRebuilding = true; + Thread.Sleep(1000); + } + + if (!wasRebuilding) + { + // No lock. Rebuild now. + var buildContext = new XmlSitemapBuildContext(store, new[] { language }) + { + CancellationToken = CancellationToken.None + }; + + await RebuildAsync(buildContext); + } + + // DRY: call self to get sitemap partition object + return await GetSitemapPartAsync(index, true); } - public bool IsGenerated + private bool SitemapFileExists(int storeId, int languageId, int index, out string path, out string name) { - get - { - string cacheKey = XMLSITEMAP_DOCUMENT_KEY.FormatInvariant(0, - _services.StoreContext.CurrentStore.Id, - _services.WorkContext.WorkingLanguage.Id); + path = BuildSitemapFilePath(storeId, languageId, index, out name); + + // Does not work reliably with symlinks due to framework caching + //var exists = _tenantFolder.FileExists(path); - return _services.Cache.Contains(cacheKey); + var exists = File.Exists(_tenantFolder.MapPath(path)); + + if (!exists) + { + path = null; + name = null; } + + return exists; + } + + private string BuildSitemapFilePath(int storeId, int languageId, int index, out string fileName) + { + fileName = SiteMapFileNamePattern.FormatInvariant(index); + return _tenantFolder.Combine(BuildSitemapDirPath(storeId, languageId), fileName); + } + + private string BuildSitemapDirPath(int storeId, int languageId) + { + return _tenantFolder.Combine(_baseDir, storeId + "/" + languageId); + } + + private string GetLockFilePath(int storeId, int languageId) + { + var fileName = LockFileNamePattern.FormatInvariant(storeId, languageId); + return _tenantFolder.Combine(_baseDir, fileName); } - public string GetSitemap(int? index = null) + public virtual async Task RebuildAsync(XmlSitemapBuildContext ctx) { - var storeId = _services.StoreContext.CurrentStore.Id; - var langId = _services.WorkContext.WorkingLanguage.Id; - var cache = _services.Cache; + Guard.NotNull(ctx, nameof(ctx)); - string cacheKey = XMLSITEMAP_DOCUMENT_KEY.FormatInvariant(0, storeId, langId); + var languageData = new Dictionary(); - if (!cache.Contains(cacheKey)) + foreach (var language in ctx.Languages) { - // The main sitemap document with index 0 does not exist, meaning: the whole sitemap - // needs to be created and cached by partitions. - lock (String.Intern(cacheKey)) + var lockFilePath = GetLockFilePath(ctx.Store.Id, language.Id); + + if (_lockFileManager.TryAcquireLock(lockFilePath, out var lockFile)) { - var prevCustomer = _services.WorkContext.CurrentCustomer; - var bot = _customerService.GetCustomerBySystemName(SystemCustomerNames.SearchEngine); + // Process only languages that are unlocked right now + // It is possible that an HTTP request triggered the generation + // of a language specific sitemap. - try + var sitemapDir = BuildSitemapDirPath(ctx.Store.Id, language.Id); + var data = new LanguageData { - // no need to vary xml sitemap by customer roles: it's relevant to crawlers only. - _services.WorkContext.CurrentCustomer = bot; + Store = ctx.Store, + Language = language, + LockFile = lockFile, + LockFilePath = lockFilePath, + TempDir = sitemapDir + "~", + FinalDir = sitemapDir, + BaseUrl = BuildBaseUrl(ctx.Store, language) + }; + + _tenantFolder.TryDeleteDirectory(data.TempDir); + _tenantFolder.CreateDirectory(data.TempDir); + + languageData[language.Id] = data; + } + } - // we need a scoped lock, because we're going to split the cache entries. - var documents = Generate(); + if (languageData.Count == 0) + { + Logger.Warn("XML sitemap rebuild already in process."); + return; + } + + var languages = languageData.Values.Select(x => x.Language); + var languageIds = languages.Select(x => x.Id).Concat(new[] { 0 }).ToArray(); + + // All sitemaps grouped by language + var sitemaps = new Multimap(); - for (int i = 0; i < documents.Count; i++) + var compositeFileLock = new ActionDisposable(() => + { + foreach (var data in languageData.Values) + { + data.LockFile.Release(); + } + }); + + using (compositeFileLock) + { + // Impersonate + var prevCustomer = _services.WorkContext.CurrentCustomer; + // no need to vary xml sitemap by customer roles: it's relevant to crawlers only. + _services.WorkContext.CurrentCustomer = _customerService.GetCustomerBySystemName(SystemCustomerNames.SearchEngine); + + try + { + var nodes = new List(); + + var queries = CreateQueries(ctx); + var total = await queries.GetTotalRecordCountAsync(); + + var totalSegments = (int)Math.Ceiling(total / (double)MaximumSiteMapNodeCount); + var hasIndex = totalSegments > 1; + var indexNodes = new Multimap(); + var segment = 0; + var numProcessed = 0; + + CheckSitemapCount(totalSegments); + + using (new DbContextScope(autoDetectChanges: false, forceNoTracking: true, proxyCreation: false, lazyLoading: false)) + { + var entities = EnumerateEntities(queries); + + foreach (var batch in entities.Slice(MaximumSiteMapNodeCount)) + { + if (ctx.CancellationToken.IsCancellationRequested) + { + break; + } + + segment++; + numProcessed = segment * MaximumSiteMapNodeCount; + ctx.ProgressCallback?.Invoke(numProcessed, total, "{0} / {1}".FormatCurrent(numProcessed, total)); + + var firstEntityName = batch.First().EntityName; + var lastEntityName = batch.Last().EntityName; + + var slugs = GetUrlRecordCollectionsForBatch(batch, languageIds); + + foreach (var data in languageData.Values) + { + var language = data.Language; + var baseUrl = data.BaseUrl; + + // Create all node entries for this segment + sitemaps[language.Id].AddRange(batch.Select(x => new XmlSitemapNode + { + LastMod = x.LastMod, + Loc = BuildNodeUrl(baseUrl, x, slugs[x.EntityName], language) + })); + + // Create index node for this segment/language combination + if (hasIndex) + { + indexNodes[language.Id].Add(new XmlSitemapNode + { + LastMod = sitemaps[language.Id].Select(x => x.LastMod).Where(x => x.HasValue).DefaultIfEmpty().Max(), + Loc = GetSitemapIndexUrl(segment, baseUrl), + }); + } + + if (segment % 5 == 0 || segment == totalSegments) + { + // Commit every 5th segment (10.000 nodes) temporarily to disk to minimize RAM usage + var documents = GetSiteMapDocuments((IReadOnlyCollection)sitemaps[language.Id]); + await SaveTempAsync(documents, data, segment - documents.Count + (hasIndex ? 1 : 0)); + + documents.Clear(); + sitemaps.RemoveAll(language.Id); + } + } + + slugs.Clear(); + + //GC.Collect(); + //GC.WaitForPendingFinalizers(); + } + + // Process custom nodes + if (!ctx.CancellationToken.IsCancellationRequested) + { + ctx.ProgressCallback?.Invoke(numProcessed, total, "Processing custom nodes".FormatCurrent(numProcessed, total)); + ProcessCustomNodes(ctx, sitemaps); + + foreach (var data in languageData.Values) + { + if (sitemaps.ContainsKey(data.Language.Id) && sitemaps[data.Language.Id].Count > 0) + { + var documents = GetSiteMapDocuments((IReadOnlyCollection)sitemaps[data.Language.Id]); + await SaveTempAsync(documents, data, (segment + 1) - documents.Count + (hasIndex ? 1 : 0)); + } + else if (segment == 0) + { + // Ensure that at least one entry exists. Otherwise, + // the system will try to rebuild again. + var homeNode = new XmlSitemapNode { LastMod = DateTime.UtcNow, Loc = data.BaseUrl }; + var documents = GetSiteMapDocuments(new List { homeNode }); + await SaveTempAsync(documents, data, 0); + } + + } + } + } + + ctx.CancellationToken.ThrowIfCancellationRequested(); + + ctx.ProgressCallback?.Invoke(totalSegments, totalSegments, "Finalizing...'"); + + foreach (var data in languageData.Values) + { + // Create index documents (if any) + if (hasIndex && indexNodes.Any()) { - // Put segment into cache - cacheKey = XMLSITEMAP_DOCUMENT_KEY.FormatInvariant(i, storeId, langId); - cache.Put(cacheKey, documents[i], TimeSpan.FromDays(1)); + var indexDocument = CreateSitemapIndexDocument(indexNodes[data.Language.Id]); + await SaveTempAsync(new List { indexDocument }, data, 0); } + + // Save finally (actually renames temp folder) + SaveFinal(data); } - finally + } + finally + { + // Undo impersonation + _services.WorkContext.CurrentCustomer = prevCustomer; + sitemaps.Clear(); + + foreach (var data in languageData.Values) { - // Undo impersonation - _services.WorkContext.CurrentCustomer = prevCustomer; + if (_tenantFolder.DirectoryExists(data.TempDir)) + { + _tenantFolder.TryDeleteDirectory(data.TempDir); + } } + + //GC.Collect(); + //GC.WaitForPendingFinalizers(); } } + } - var page = index ?? 0; - cacheKey = XMLSITEMAP_DOCUMENT_KEY.FormatInvariant(page, storeId, langId); - - if (cache.Contains(cacheKey)) + private string BuildBaseUrl(Store store, Language language) + { + var host = _services.StoreService.GetHost(store).EnsureEndsWith("/"); + + var locSettings = _services.Settings.LoadSetting(store.Id); + if (locSettings.SeoFriendlyUrlsForLanguagesEnabled) { - return cache.Get(cacheKey); + var defaultLangId = _languageService.GetDefaultLanguageId(store.Id); + if (language.Id != defaultLangId || locSettings.DefaultLanguageRedirectBehaviour < DefaultLanguageRedirectBehaviour.StripSeoCode) + { + host += language.GetTwoLetterISOLanguageName() + "/"; + } } - return null; + return host; } - /// - /// Gets the collection of XML sitemap documents for the current site. If there are less than 1.000 sitemap - /// nodes, only one sitemap document will exist in the collection, otherwise a sitemap index document will be - /// the first entry in the collection and all other entries will be sitemap XML documents. - /// - /// A collection of XML sitemap documents. - protected IList Generate() + private string BuildNodeUrl(string baseUrl, NamedEntity entity, UrlRecordCollection slugs, Language language) + { + return baseUrl + slugs.GetSlug(language.Id, entity.Id, true); + } + + private async Task SaveTempAsync(List documents, LanguageData data, int start) + { + for (int i = 0; i < documents.Count; i++) + { + // Save segment to disk + var fileName = SiteMapFileNamePattern.FormatInvariant(i + start); + var filePath = _tenantFolder.Combine(data.TempDir, fileName); + + await _tenantFolder.CreateTextFileAsync(filePath, documents[i]); + } + } + + private void SaveFinal(LanguageData data) { - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; + // Delete current sitemap dir + _tenantFolder.TryDeleteDirectory(data.FinalDir); - var nodes = new List(); + var source = _tenantFolder.MapPath(data.TempDir); + var dest = _tenantFolder.MapPath(data.FinalDir); - using (var scope = new DbContextScope(autoDetectChanges: false, forceNoTracking: true, proxyCreation: false, lazyLoading: false)) + // Move/Rename new (temp) dir to current + System.IO.Directory.Move(source, dest); + + int retries = 0; + while (!SitemapFileExists(data.Store.Id, data.Language.Id, 0, out _, out _)) { + if (retries > 20) + { + break; + } + + // IO breathe: directly after a folder rename a file check fails. Wait a sec... + Task.Delay(500).Wait(); + retries++; + } + } - if (_seoSettings.XmlSitemapIncludesCategories) + private IEnumerable EnumerateEntities(QueryHolder queries) + { + if (queries.Categories != null) + { + var categories = queries.Categories.Select(x => new { x.Id, x.UpdatedOnUtc }).ToList(); + foreach (var x in categories) { - nodes.AddRange(GetCategoryNodes(0, protocol)); + yield return new NamedEntity { EntityName = "Category", Id = x.Id, LastMod = x.UpdatedOnUtc }; } + } - if (_seoSettings.XmlSitemapIncludesManufacturers) + if (queries.Manufacturers != null) + { + var manufacturers = queries.Manufacturers.Select(x => new { x.Id, x.UpdatedOnUtc }).ToList(); + foreach (var x in manufacturers) { - nodes.AddRange(GetManufacturerNodes(protocol)); + yield return new NamedEntity { EntityName = "Manufacturer", Id = x.Id, LastMod = x.UpdatedOnUtc }; } + } - if (_seoSettings.XmlSitemapIncludesTopics) + if (queries.Topics != null) + { + var topics = queries.Topics.Select(x => new { x.Id }).ToList(); + foreach (var x in topics) { - nodes.AddRange(GetTopicNodes(protocol)); + yield return new NamedEntity { EntityName = "Topic", Id = x.Id, LastMod = DateTime.UtcNow }; } + } + + if (queries.Products != null) + { + var query = queries.Products.AsNoTracking(); + var maxId = int.MaxValue; - if (_seoSettings.XmlSitemapIncludesProducts) + //var limit = 0; + while (maxId > 1) { - nodes.AddRange(GetProductNodes(protocol)); + var products = query + .Where(x => x.Id < maxId) + .OrderByDescending(x => x.Id) + .Take(() => MaximumSiteMapNodeCount) + .Select(x => new { x.Id, x.UpdatedOnUtc }) + .ToList(); + + //limit++; + //if (limit >= 100) + //{ + // break; + //} + + if (products.Count == 0) + { + break; + } + + maxId = products.Last().Id; + + foreach (var x in products) + { + yield return new NamedEntity { EntityName = "Product", Id = x.Id, LastMod = x.UpdatedOnUtc }; + } } } + } + + private IDictionary GetUrlRecordCollectionsForBatch(IEnumerable batch, int[] languageIds) + { + var result = new Dictionary(); - var customNodes = GetCustomNodes(protocol); - if (customNodes != null) + if (batch.First().EntityName == "Product") { - nodes.AddRange(customNodes); + // nothing comes after product + int min = batch.Last().Id; + int max = batch.First().Id; + + result["Product"] = _urlRecordService.GetUrlRecordCollection("Product", languageIds, new[] { min, max }, true, true); } - var documents = GetSiteMapDocuments(nodes.AsReadOnly()); + var entityGroups = batch.ToMultimap(x => x.EntityName, x => x.Id); + foreach (var group in entityGroups) + { + var isRange = group.Key == "Product"; + var entityIds = isRange ? new[] { group.Value.Last(), group.Value.First() } : group.Value.ToArray(); + + result[group.Key] = _urlRecordService.GetUrlRecordCollection(group.Key, languageIds, entityIds, isRange, isRange); + } - return documents; + return result; } protected virtual List GetSiteMapDocuments(IReadOnlyCollection nodes) { - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - int siteMapCount = (int)Math.Ceiling(nodes.Count / (double)MaximumSiteMapNodeCount); CheckSitemapCount(siteMapCount); @@ -231,66 +580,14 @@ protected virtual List GetSiteMapDocuments(IReadOnlyCollection(siteMapCount); - if (siteMapCount > 1) - { - var xml = this.GetSitemapIndexDocument(siteMaps, protocol); - siteMapDocuments.Add(xml); - } - foreach (var kvp in siteMaps) { - var xml = this.GetSitemapDocument(kvp.Value); - siteMapDocuments.Add(xml); + siteMapDocuments.Add(this.GetSitemapDocument(kvp.Value)); } return siteMapDocuments; } - /// - /// Gets the sitemap index XML document, containing links to all the sitemap XML documents. - /// - /// The collection of sitemaps containing their index and nodes. - /// The sitemap index XML document, containing links to all the sitemap XML documents. - private string GetSitemapIndexDocument(IEnumerable>> siteMaps, string protocol) - { - XNamespace ns = SiteMapsNamespace; - - XElement root = new XElement(ns + "sitemapindex"); - - foreach (KeyValuePair> map in siteMaps) - { - // Get the latest LastModified DateTime from the sitemap nodes or null if there is none. - DateTime? lastModified = map.Value - .Select(x => x.LastMod) - .Where(x => x.HasValue) - .DefaultIfEmpty() - .Max(); - - var xel = new XElement( - ns + "sitemap", - new XElement(ns + "loc", this.GetSitemapUrl(map.Key, protocol)), - lastModified.HasValue ? - new XElement( - ns + "lastmod", - lastModified.Value.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz")) : - null); - - root.Add(xel); - } - - var document = new XDocument(root); - var xml = document.ToString(SaveOptions.DisableFormatting); - CheckDocumentSize(xml); - - return xml; - } - - private string GetSitemapUrl(int index, string protocol) - { - var url = _urlHelper.RouteUrl("SitemapSEO", new { index = index }, protocol); - return url; - } - /// /// Gets the sitemap XML document for the specified set of nodes. /// @@ -299,7 +596,7 @@ private string GetSitemapUrl(int index, string protocol) private string GetSitemapDocument(IEnumerable nodes) { //var languages = _languageService.GetAllLanguages(); - + XNamespace ns = SiteMapsNamespace; XNamespace xhtml = XhtmlNamespace; @@ -354,114 +651,91 @@ private string GetSitemapDocument(IEnumerable nodes) return xml; } - protected virtual IEnumerable GetCategoryNodes(int parentCategoryId, string protocol) + /// + /// Gets the sitemap index XML document, containing links to all the sitemap XML documents. + /// + /// The collection of sitemaps containing their index and nodes. + /// The sitemap index XML document, containing links to all the sitemap XML documents. + private string CreateSitemapIndexDocument(IEnumerable nodes) { - var categories = _categoryService.GetAllCategories(showHidden: false, storeId: _services.StoreContext.CurrentStore.Id); + XNamespace ns = SiteMapsNamespace; - _services.DbContext.DetachAll(); + XElement root = new XElement(ns + "sitemapindex"); - return categories.Select(x => + foreach (var node in nodes) { - var node = new XmlSitemapNode - { - Loc = _urlHelper.RouteUrl("Category", new { SeName = x.GetSeName() }, protocol), - LastMod = x.UpdatedOnUtc, - //ChangeFreq = ChangeFrequency.Weekly, - //Priority = 0.8f - }; + var xel = new XElement( + ns + "sitemap", + new XElement(ns + "loc", node.Loc), + node.LastMod.HasValue ? + new XElement( + ns + "lastmod", + node.LastMod.Value.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz")) : + null); - // TODO: add hreflang links if LangCount is > 1 and PrependSeoCode is true + root.Add(xel); + } - return node; - }); + var document = new XDocument(root); + var xml = document.ToString(SaveOptions.DisableFormatting); + CheckDocumentSize(xml); + + return xml; } - protected virtual IEnumerable GetManufacturerNodes(string protocol) + private string GetSitemapIndexUrl(int index, string baseUrl) { - var manufacturers = _manufacturerService.GetAllManufacturers(false); - - _services.DbContext.DetachAll(); - - return manufacturers.Select(x => - { - var node = new XmlSitemapNode - { - Loc = _urlHelper.RouteUrl("Manufacturer", new { SeName = x.GetSeName() }, protocol), - LastMod = x.UpdatedOnUtc, - //ChangeFreq = ChangeFrequency.Weekly, - //Priority = 0.8f - }; - - // TODO: add hreflang links if LangCount is > 1 and PrependSeoCode is true - - return node; - }); + var url = _urlHelper.RouteUrl("XmlSitemap", new { index }).TrimStart('/'); + return baseUrl + url; } - protected virtual IEnumerable GetTopicNodes(string protocol) + private QueryHolder CreateQueries(XmlSitemapBuildContext ctx) { - var topics = _topicService.GetAllTopics(_services.StoreContext.CurrentStore.Id).ToList().FindAll(t => t.IncludeInSitemap && !t.RenderAsWidget); - - _services.DbContext.DetachAll(); + var storeId = ctx.Store.Id; - return topics.Select(x => + if (_services.StoreService.IsSingleStoreMode()) { - var node = new XmlSitemapNode - { - Loc = _urlHelper.RouteUrl("Topic", new { SystemName = x.SystemName }, protocol), - LastMod = DateTime.UtcNow, - //ChangeFreq = ChangeFrequency.Weekly, - //Priority = 0.8f - }; - - // TODO: add hreflang links if LangCount is > 1 and PrependSeoCode is true - - return node; - }); - } + storeId = 0; + } - protected virtual IEnumerable GetProductNodes(string protocol) - { - var nodes = new List(); + // Always work with store-dependant setting + var seoSettings = _services.Settings.LoadSetting(storeId); - var searchQuery = new CatalogSearchQuery() - .VisibleOnly() - .VisibleIndividuallyOnly(true) - .HasStoreId(_services.StoreContext.CurrentStoreIdIfMultiStoreMode); + var holder = new QueryHolder(); - var query = _catalogSearchService.PrepareQuery(searchQuery); - query = query.OrderByDescending(x => x.CreatedOnUtc); + if (seoSettings.XmlSitemapIncludesCategories) + { + holder.Categories = _categoryService.BuildCategoriesQuery(showHidden: false, storeId: storeId); + } - for (var pageIndex = 0; pageIndex < 9999999; ++pageIndex) + if (seoSettings.XmlSitemapIncludesManufacturers) { - var products = new PagedList(query, pageIndex, 1000); + holder.Manufacturers = _manufacturerService.GetManufacturers(false).OrderBy(x => x.DisplayOrder).ThenBy(x => x.Name); + } - nodes.AddRange(products.Select(x => + if (seoSettings.XmlSitemapIncludesTopics) + { + holder.Topics = _topicService.GetAllTopics(storeId).AlterQuery(q => { - var node = new XmlSitemapNode - { - Loc = _urlHelper.RouteUrl("Product", new { SeName = x.GetSeName() }, protocol), - LastMod = x.UpdatedOnUtc, - //ChangeFreq = ChangeFrequency.Weekly, - //Priority = 0.8f - }; - - // TODO: add hreflang links if LangCount is > 1 and PrependSeoCode is true - return node; - })); + return q.Where(t => t.IncludeInSitemap && !t.RenderAsWidget); + }).SourceQuery; + } - _services.DbContext.DetachAll(); + if (seoSettings.XmlSitemapIncludesProducts) + { + var searchQuery = new CatalogSearchQuery() + .VisibleOnly(_services.WorkContext.CurrentCustomer) + .VisibleIndividuallyOnly(true) + .HasStoreId(storeId); - if (!products.HasNextPage) - break; + holder.Products = _catalogSearchService.PrepareQuery(searchQuery); } - return nodes; + return holder; } - protected virtual IEnumerable GetCustomNodes(string protocol) + protected void ProcessCustomNodes(XmlSitemapBuildContext ctx, Multimap sitemaps) { - return Enumerable.Empty(); } /// @@ -488,5 +762,76 @@ private void CheckSitemapCount(int sitemapCount) Logger.Warn(ex, ex.Message); } } - } + + public bool IsRebuilding(int storeId, int languageId) + { + return IsRebuilding(GetLockFilePath(storeId, languageId)); + } + + private bool IsRebuilding(string lockFilePath) + { + return _lockFileManager.IsLocked(lockFilePath); + } + + public virtual bool IsGenerated(int storeId, int languageId) + { + return SitemapFileExists(storeId, languageId, 0, out _, out _); + } + + public virtual void Invalidate(int storeId, int languageId) + { + var dir = BuildSitemapDirPath(storeId, languageId); + + if (_tenantFolder.DirectoryExists(dir)) + { + _tenantFolder.DeleteDirectory(dir); + } + } + + #region Nested classes + + class LanguageData + { + public Store Store { get; set; } + public Language Language { get; set; } + public ILockFile LockFile { get; set; } + public string LockFilePath { get; set; } + public string TempDir { get; set; } + public string FinalDir { get; set; } + public string BaseUrl { get; set; } + } + + class QueryHolder + { + public IQueryable Categories { get; set; } + public IQueryable Manufacturers { get; set; } + public IQueryable Topics { get; set; } + public IQueryable Products { get; set; } + + public async Task GetTotalRecordCountAsync() + { + int num = 0; + if (Categories != null) num += await Categories.CountAsync(); + if (Manufacturers != null) num += await Manufacturers.CountAsync(); + if (Topics != null) num += await Topics.CountAsync(); + if (Products != null) num += await Products.CountAsync(); + //if (Products != null) num += 200000; + + return num; + } + } + + class NamedEntity : BaseEntity, ISlugSupported + { + public string EntityName { get; set; } + public DateTime LastMod { get; set; } + + public override string GetEntityName() + { + return EntityName; + } + } + + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Seo/XmlSitemapPartition.cs b/src/Libraries/SmartStore.Services/Seo/XmlSitemapPartition.cs new file mode 100644 index 0000000000..ec5e519913 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Seo/XmlSitemapPartition.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace SmartStore.Services.Seo +{ + public class XmlSitemapPartition + { + public string Name { get; set; } + public int Index { get; set; } + public int StoreId { get; set; } + public int LanguageId { get; set; } + public DateTime ModifiedOnUtc { get; set; } + public Stream Stream { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 5f72a1cadf..723ecf1212 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -1,5 +1,6 @@  - + + Debug AnyCPU @@ -33,6 +34,7 @@ prompt 4 false + latest pdbonly @@ -42,6 +44,7 @@ prompt 4 false + latest true @@ -51,6 +54,7 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest true @@ -60,14 +64,14 @@ AnyCPU prompt MinimumRecommendedRules.ruleset + latest - ..\..\packages\AngleSharp.0.9.9\lib\net45\AngleSharp.dll + ..\..\packages\AngleSharp.0.9.11\lib\net45\AngleSharp.dll - - ..\..\packages\Autofac.4.5.0\lib\net45\Autofac.dll - True + + ..\..\packages\Autofac.4.9.0\lib\net45\Autofac.dll ..\..\packages\Autofac.Mvc5.4.0.2\lib\net45\Autofac.Integration.Mvc.dll @@ -85,44 +89,51 @@ ..\..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll True - - ..\..\packages\EPPlus.4.1.0\lib\net40\EPPlus.dll - True + + ..\..\packages\EPPlus.4.5.3.1\lib\net40\EPPlus.dll - - ..\..\packages\ImageProcessor.2.5.6\lib\net45\ImageProcessor.dll - True + + ..\..\packages\ImageProcessor.2.7.0.100\lib\net452\ImageProcessor.dll - - ..\..\packages\LumenWorksCsvReader.3.9.1\lib\net20\LumenWorks.Framework.IO.dll - True + + ..\..\packages\LumenWorksCsvReader.4.0.0\lib\net461\LumenWorks.Framework.IO.dll + + + ..\..\packages\MaxMind.Db.2.4.0\lib\net45\MaxMind.Db.dll - - ..\..\packages\MaxMind.GeoIP.2.1.17\lib\net35\MaxMind.GeoIP.dll + + ..\..\packages\MaxMind.GeoIP2.3.0.0\lib\net45\MaxMind.GeoIP2.dll True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + ..\..\packages\Microsoft.Web.Xdt.3.0.0\lib\net40\Microsoft.Web.XmlTransform.dll + ..\..\packages\ncrontab.3.3.0\lib\net35\NCrontab.dll True - - ..\..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll - True + + ..\..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll ..\..\packages\NReco.PdfGenerator.1.1.15\lib\net20\NReco.PdfGenerator.dll True - - ..\..\packages\PreMailer.Net.1.5.5\lib\net45\PreMailer.Net.dll + + ..\..\packages\NuGet.Core.2.14.0\lib\net40-Client\NuGet.Core.dll + + + ..\..\packages\PreMailer.Net.2.0.1\lib\net45\PreMailer.Net.dll + + @@ -132,35 +143,30 @@ True + - - ..\..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + + ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.Helpers.dll - - False - ..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll + + ..\..\packages\Microsoft.AspNet.Mvc.5.2.7\lib\net45\System.Web.Mvc.dll - False - ..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + ..\..\packages\Microsoft.AspNet.Razor.3.2.7\lib\net45\System.Web.Razor.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Deployment.dll - False - ..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + ..\..\packages\Microsoft.AspNet.WebPages.3.2.7\lib\net45\System.Web.WebPages.Razor.dll @@ -194,11 +200,33 @@ + + + + + + + + + + + + + + + + - + + + + + + + @@ -218,11 +246,13 @@ + + @@ -253,6 +283,7 @@ + @@ -263,6 +294,9 @@ + + + @@ -270,9 +304,11 @@ + + @@ -390,11 +426,8 @@ - - - @@ -404,7 +437,6 @@ - @@ -520,20 +552,33 @@ - + - - - - - + + + + + - - - - - + + + + + + + + + + + + + + + + + + @@ -551,6 +596,8 @@ + + @@ -582,6 +629,7 @@ + diff --git a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs index 7eed774f70..02e9f61346 100644 --- a/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/IStoreMappingService.cs @@ -107,7 +107,7 @@ public static int[] GetStoresIdsWithAccess(this IStoreMappingService svc, T e if (entity == null) return new int[0]; - return svc.GetStoresIdsWithAccess(typeof(T).Name, entity.Id); + return svc.GetStoresIdsWithAccess(entity.GetEntityName(), entity.Id); } /// @@ -124,7 +124,7 @@ public static bool Authorize(this IStoreMappingService svc, T entity) where T if (!entity.LimitedToStores) return true; - return svc.Authorize(typeof(T).Name, entity.Id); + return svc.Authorize(entity.GetEntityName(), entity.Id); } /// @@ -142,7 +142,7 @@ public static bool Authorize(this IStoreMappingService svc, T entity, int sto if (!entity.LimitedToStores) return true; - return svc.Authorize(typeof(T).Name, entity.Id, storeId); + return svc.Authorize(entity.GetEntityName(), entity.Id, storeId); } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs index ef4f36513c..d5578486ca 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) @@ -56,7 +60,7 @@ public virtual IList GetStoreMappings(T entity) where T : BaseE Guard.NotNull(entity, nameof(entity)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); var query = from sm in _storeMappingRepository.Table where sm.EntityId == entityId && @@ -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 @@ -117,7 +121,7 @@ public virtual void InsertStoreMapping(T entity, int storeId) where T : BaseE throw new ArgumentOutOfRangeException(nameof(storeId)); int entityId = entity.Id; - string entityName = typeof(T).Name; + string entityName = entity.GetEntityName(); var storeMapping = new StoreMapping { @@ -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..f8cde15ab0 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -1,40 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; -using SmartStore.Data; using SmartStore.Core.Data; using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Events; using SmartStore.Data.Caching; -using SmartStore.Services.Media; -using SmartStore.Core.Domain.Security; namespace SmartStore.Services.Stores { public partial class StoreService : IStoreService { private readonly IRepository _storeRepository; - private readonly IEventPublisher _eventPublisher; - private readonly SecuritySettings _securitySettings; private bool? _isSingleStoreMode = null; - public StoreService(IRepository storeRepository, IEventPublisher eventPublisher, SecuritySettings securitySettings) + public StoreService(IRepository storeRepository) { _storeRepository = storeRepository; - _eventPublisher = eventPublisher; - _securitySettings = securitySettings; - } - - public virtual void DeleteStore(Store store) - { - Guard.NotNull(store, nameof(store)); - - var allStores = GetAllStores(); - if (allStores.Count == 1) - throw new Exception("You cannot delete the only configured store."); - - _storeRepository.Delete(store); } public virtual IList GetAllStores() @@ -71,6 +52,17 @@ public virtual void UpdateStore(Store store) _storeRepository.Update(store); } + public virtual void DeleteStore(Store store) + { + Guard.NotNull(store, nameof(store)); + + var allStores = GetAllStores(); + if (allStores.Count == 1) + throw new Exception("You cannot delete the only configured store."); + + _storeRepository.Delete(store); + } + public virtual bool IsSingleStoreMode() { if (!_isSingleStoreMode.HasValue) @@ -116,7 +108,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..be442aa61a 100644 --- a/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/IScheduleTaskService.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using SmartStore.Core; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks @@ -40,26 +42,7 @@ public partial interface IScheduleTaskService /// Gets all pending tasks /// /// 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(); + Task> GetPendingTasksAsync(); /// /// Inserts a task @@ -97,9 +80,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/ITask.cs b/src/Libraries/SmartStore.Services/Tasks/ITask.cs index 4cc75d9a75..01e576c330 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITask.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITask.cs @@ -1,17 +1,42 @@  +using System; +using System.Threading.Tasks; + namespace SmartStore.Services.Tasks { /// - /// Interface that should be implemented by each task + /// Interface that should be implemented by background tasks /// public partial interface ITask { /// - /// Execute task + /// Executes a task synchronously /// /// /// The execution context /// void Execute(TaskExecutionContext ctx); } + + /// + /// Interface that should be implemented by background tasks + /// + public interface IAsyncTask : ITask + { + /// + /// Executes a task asynchronously + /// + /// + /// The execution context + /// + Task ExecuteAsync(TaskExecutionContext ctx); + } + + public abstract class AsyncTask : IAsyncTask + { + public void Execute(TaskExecutionContext ctx) => + throw new NotSupportedException(); + + public abstract Task ExecuteAsync(TaskExecutionContext ctx); + } } diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs index 053cf45937..5f0f126359 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading.Tasks; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks { public interface ITaskExecutor { - void Execute( - ScheduleTask task, + Task ExecuteAsync( + ScheduleTask entity, IDictionary taskParameters = null, bool throwOnError = false); } 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/InitializeSchedulerFilter.cs b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs index 8a92d0abe2..665fca2d30 100644 --- a/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs +++ b/src/Libraries/SmartStore.Services/Tasks/InitializeSchedulerFilter.cs @@ -32,15 +32,7 @@ public void OnAuthentication(AuthenticationContext filterContext) var eventPublisher = EngineContext.Current.Resolve(); var logger = EngineContext.Current.Resolve().CreateLogger(); - - // The very first request must set app state to 'fully initialized' - if (!EngineContext.Current.IsFullyInitialized) - { - EngineContext.Current.IsFullyInitialized = true; - eventPublisher.Publish(new AppStartedEvent { HttpContext = filterContext.HttpContext }); - } - - ITaskScheduler taskScheduler = EngineContext.Current.Resolve(); + var taskScheduler = EngineContext.Current.Resolve(); try { diff --git a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs index 2756605ef8..0b4e3ef725 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ScheduleTaskService.cs @@ -1,28 +1,41 @@ using System; using System.Collections.Generic; -using System.Data.Entity.Infrastructure; +using System.Data.Entity.Core; +using System.Data.SqlClient; using System.Linq; +using System.Threading.Tasks; +using System.Data.Entity; +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 +78,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(); } @@ -87,58 +100,41 @@ public virtual IList GetAllTasks(bool includeDisabled = false) RetryOnDeadlockException); } - public virtual IList GetPendingTasks() + public async virtual Task> GetPendingTasksAsync() { 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 = await Retry.RunAsync( + () => query.ToListAsync(), + 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 +148,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 +189,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 +203,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 +282,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..fa52d51967 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,30 @@ namespace SmartStore.Services.Tasks { + public delegate void ProgressCallback(int value, int maximum, string message); + /// /// 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 +45,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 +79,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 +127,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 +147,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..93e71a4f7a 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,47 +1,41 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; +using System.Threading.Tasks; 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; @@ -50,32 +44,49 @@ public TaskExecutor( public ILogger Logger { get; set; } public Localizer T { get; set; } - public void Execute( - ScheduleTask task, + public async Task ExecuteAsync( + ScheduleTask entity, IDictionary taskParameters = null, bool throwOnError = false) { - if (task.IsRunning) + if (AsyncRunner.AppShutdownCancellationToken.IsCancellationRequested) + { return; + } - if (AsyncRunner.AppShutdownCancellationToken.IsCancellationRequested) - return; + if (entity.LastHistoryEntry == null) + { + // The task was started manually. + entity.LastHistoryEntry = _scheduledTaskService.GetLastHistoryEntryByTaskId(entity.Id); + } + + if (entity?.LastHistoryEntry?.IsRunning == true) + { + return; + } bool faulted = false; bool canceled = false; string lastError = null; - ITask instance = null; + ITask task = null; string stateName = null; - Type taskType = null; + Exception exception = null; - try - { - taskType = Type.GetType(task.Type); + var historyEntry = new ScheduleTaskHistory + { + ScheduleTaskId = entity.Id, + IsRunning = true, + MachineName = _env.MachineName.EmptyNull(), + StartedOnUtc = DateTime.UtcNow + }; + try + { + taskType = Type.GetType(entity.Type); if (taskType == null) { - Logger.DebugFormat("Invalid scheduled task type: {0}", task.Type.NaIfEmpty()); + Logger.DebugFormat("Invalid scheduled task type: {0}", entity.Type.NaIfEmpty()); } if (taskType == null) @@ -83,92 +94,118 @@ public void Execute( if (!PluginManager.IsActivePluginAssembly(taskType.Assembly)) return; - } - catch + + entity.ScheduleTaskHistory.Add(historyEntry); + _scheduledTaskService.UpdateTask(entity); + } + catch { return; } try { - // 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 + // Task history entry has been successfully added, now we execute the task. + // Create task instance. + task = _taskResolver(taskType); + stateName = entity.Id.ToString(); + + // 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() }; - Logger.DebugFormat("Executing scheduled task: {0}", task.Type); - instance.Execute(ctx); + Logger.DebugFormat("Executing scheduled task: {0}", entity.Type); + + if (task is IAsyncTask asyncTask) + { + await asyncTask.ExecuteAsync(ctx); + } + else + { + task.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.ToAllMessages(true); - if (throwOnError) + if (canceled) + { + Logger.Warn(ex, T("Admin.System.ScheduleTasks.Cancellation", entity.Name)); + } + else { - throw; + Logger.Error(ex, string.Concat(T("Admin.System.ScheduleTasks.RunningError", entity.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) + if ((!canceled && entity.StopOnError) || task == null) { - task.Enabled = false; + entity.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.", entity.Type, (now - historyEntry.StartedOnUtc).TotalMilliseconds); - if (task.Enabled) - { - task.NextRunUtc = _scheduledTaskService.GetNextSchedule(task); - } + // Remove from AsyncState. + if (stateName.HasValue()) + { + _asyncState.Remove(stateName); + } + } + catch (Exception ex) + { + Logger.Error(ex); + } - // remove from AsyncState - if (stateName.HasValue()) + if (entity.Enabled) { - _asyncState.Remove(stateName); - } + entity.NextRunUtc = _scheduledTaskService.GetNextSchedule(entity); + updateTask = true; + } + + _scheduledTaskService.UpdateHistoryEntry(historyEntry); - _scheduledTaskService.UpdateTask(task); + if (updateTask) + { + _scheduledTaskService.UpdateTask(entity); + } + + 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/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 131d0239c0..f8fca60dc9 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -112,7 +112,7 @@ private string CreateAuthToken() private string GenerateAuthTokenCacheKey(string authToken) { - return "Scheduler.AuthToken." + authToken; + return "Scheduler:AuthToken:" + authToken; } public bool VerifyAuthToken(string authToken) diff --git a/src/Libraries/SmartStore.Services/Tax/TaxService.cs b/src/Libraries/SmartStore.Services/Tax/TaxService.cs index bcf14821e3..6d5cb78344 100644 --- a/src/Libraries/SmartStore.Services/Tax/TaxService.cs +++ b/src/Libraries/SmartStore.Services/Tax/TaxService.cs @@ -140,15 +140,9 @@ protected virtual bool IsEuConsumer(Customer customer) return true; } - var country = address == null ? null : address.Country; - - if (country == null) - { - // No Country or BillingAddress set: try to resolve country from IP address - _geoCountryLookup.IsEuIpAddress(customer.LastIpAddress, out country); - } - - if (country == null || !country.SubjectToVat) + // No Country or BillingAddress set: try to resolve country from IP address + var subjectToVat = _geoCountryLookup.LookupCountry(customer.LastIpAddress)?.IsInEu == true; + if (!subjectToVat) { 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..07152bf79b 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; } @@ -41,37 +49,73 @@ public virtual Topic GetTopicById(int topicId) if (topicId == 0) return null; - return _topicRepository.GetById(topicId); + return _topicRepository.GetByIdCached(topicId, "db.topic.id-" + 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 topics (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/app.config b/src/Libraries/SmartStore.Services/app.config index 48f67fefef..08f9834796 100644 --- a/src/Libraries/SmartStore.Services/app.config +++ b/src/Libraries/SmartStore.Services/app.config @@ -21,7 +21,7 @@ - + @@ -29,12 +29,20 @@ - + + + + + + + + + diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index dc8b84bdc8..be5db3727d 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -1,23 +1,27 @@  - - + + - - - - - - - + + + + + + + + + + - + - + + - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs index 0c44f89540..35fbe06a06 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayCheckoutController.cs @@ -1,25 +1,34 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Web; using System.Web.Mvc; using SmartStore.AmazonPay.Services; +using SmartStore.Core.Html; +using SmartStore.Core.Logging; using SmartStore.Services.Common; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; namespace SmartStore.AmazonPay.Controllers { - public class AmazonPayCheckoutController : AmazonPayControllerBase + public class AmazonPayCheckoutController : AmazonPayControllerBase { private readonly HttpContextBase _httpContext; private readonly IAmazonPayService _apiService; private readonly IGenericAttributeService _genericAttributeService; + private readonly IOrderProcessingService _orderProcessingService; - public AmazonPayCheckoutController( + public AmazonPayCheckoutController( HttpContextBase httpContext, IAmazonPayService apiService, - IGenericAttributeService genericAttributeService) + IGenericAttributeService genericAttributeService, + IOrderProcessingService orderProcessingService) { _httpContext = httpContext; _apiService = apiService; _genericAttributeService = genericAttributeService; + _orderProcessingService = orderProcessingService; } public ActionResult OrderReferenceCreated(string orderReferenceId/*, string accessToken*/) @@ -49,9 +58,9 @@ public ActionResult OrderReferenceCreated(string orderReferenceId/*, string acce error = error.Grow(T("Plugins.Payments.AmazonPay.MissingAddressConsentToken"), " "); } } - catch (Exception exception) + catch (Exception ex) { - error = exception.Message; + error = ex.Message; } return new JsonResult { Data = new { success = success, error = error } }; @@ -79,8 +88,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 = "" }); } @@ -99,5 +116,97 @@ public ActionResult CheckoutCompleted() return new EmptyResult(); } - } + + #region Confirmation flow + + // Ajax. + public ActionResult ConfirmOrder(string formData) + { + string redirectUrl = null; + var messages = new List(); + var success = false; + + try + { + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var processPaymentRequest = (_httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest) ?? new ProcessPaymentRequest(); + + processPaymentRequest.StoreId = store.Id; + processPaymentRequest.CustomerId = customer.Id; + processPaymentRequest.PaymentMethodSystemName = AmazonPayPlugin.SystemName; + + // We must check here if an order can be placed to avoid creating unauthorized Amazon payment objects. + var warnings = _orderProcessingService.GetOrderPlacementWarnings(processPaymentRequest); + + if (!warnings.Any()) + { + if (_orderProcessingService.IsMinimumOrderPlacementIntervalValid(customer, store)) + { + if (_apiService.ConfirmOrderReference()) + { + success = true; + + var state = _httpContext.GetAmazonPayState(Services.Localization); + state.FormData = formData.EmptyNull(); + } + else + { + _httpContext.Session["AmazonPayFailedPaymentReason"] = "PaymentMethodNotAllowed"; + + redirectUrl = Url.Action("PaymentMethod", "Checkout", new { area = "" }); + } + } + else + { + messages.Add(T("Checkout.MinOrderPlacementInterval")); + } + } + else + { + messages.AddRange(warnings.Select(x => HtmlUtils.ConvertPlainTextToHtml(x))); + } + } + catch (Exception ex) + { + messages.Add(ex.Message); + Logger.Error(ex); + } + + return Json(new { success, redirectUrl, messages }); + } + + public ActionResult ConfirmationResult(string authenticationStatus) + { + var state = _httpContext.GetAmazonPayState(Services.Localization); + state.SubmitForm = false; + + if (authenticationStatus.IsCaseInsensitiveEqual("Success")) + { + state.SubmitForm = true; + return RedirectToAction("Confirm", "Checkout", new { area = "" }); + } + else if (authenticationStatus.IsCaseInsensitiveEqual("Failure")) + { + // The buyer has exhausted their retry attempts and payment instrument selection on the Amazon Pay page. + // Review: redirect back to shopping cart (like AmazonRejected). + _httpContext.Session["AmazonPayFailedPaymentReason"] = "AuthenticationStatusFailure"; + + return RedirectToRoute("ShoppingCart"); + } + else + { + // authenticationStatus == "Abandoned": The buyer took action to close/cancel the MFA challenge. + // Review: redirect to checkout payment page (like InvalidPaymentMethod). + state.IsConfirmed = false; + state.FormData = null; + + _httpContext.Session["AmazonPayFailedPaymentReason"] = "AuthenticationStatusAbandoned"; + + return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); + } + } + + #endregion + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs index d4515bf3e2..669cc4c009 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayController.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Web; using System.Web.Mvc; using SmartStore.AmazonPay.Models; using SmartStore.AmazonPay.Services; using SmartStore.ComponentModel; using SmartStore.Core.Domain.Customers; using SmartStore.Services.Authentication.External; +using SmartStore.Services.Customers; using SmartStore.Services.Payments; using SmartStore.Services.Tasks; using SmartStore.Web.Framework; @@ -17,17 +21,20 @@ namespace SmartStore.AmazonPay.Controllers { public class AmazonPayController : PaymentControllerBase { - private readonly IAmazonPayService _apiService; + private readonly HttpContextBase _httpContext; + private readonly IAmazonPayService _apiService; private readonly Lazy _scheduleTaskService; private readonly Lazy _openAuthenticationService; private readonly Lazy _externalAuthenticationSettings; public AmazonPayController( - IAmazonPayService apiService, + HttpContextBase httpContext, + IAmazonPayService apiService, Lazy scheduleTaskService, Lazy openAuthenticationService, Lazy externalAuthenticationSettings) { + _httpContext = httpContext; _apiService = apiService; _scheduleTaskService = scheduleTaskService; _openAuthenticationService = openAuthenticationService; @@ -59,17 +66,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 +106,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 +124,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)] @@ -130,9 +155,57 @@ public ActionResult IPNHandler() return Content("OK"); } - // Authentication + // For payment simulation in sandbox mode. Admin and sandbox only. + public ActionResult SetSellerNote(string paymentState) + { + var customer = Services.WorkContext.CurrentCustomer; + if (!customer.IsAdmin()) + { + return HttpNotFound(); + } + + var store = Services.StoreContext.CurrentStore; + var settings = Services.Settings.LoadSetting(store.Id); + + if (!settings.UseSandbox) + { + return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Endpoint is for testing purposes and only available in sandbox mode."); + } - [ChildActionOnly] + var state = _httpContext.GetAmazonPayState(Services.Localization); + var sellerNote = string.Empty; // Reset payment state. + + // See https://pay.amazon.com/de/developer/documentation/lpwa/201956480 + if (paymentState.IsCaseInsensitiveEqual("PaymentMethodNotAllowed")) + { + sellerNote = "{\"SandboxSimulation\":{\"Constraint\":\"PaymentMethodNotAllowed\"}}"; + } + else if (paymentState.IsCaseInsensitiveEqual("Success")) + { + sellerNote = "{\"SandboxSimulation\":{\"PaymentAuthenticationStatus\":{\"State\":\"Success\"}}}"; + } + else if (paymentState.IsCaseInsensitiveEqual("Failure")) + { + sellerNote = "{\"SandboxSimulation\":{\"PaymentAuthenticationStatus\":{\"State\":\"Failure\"}}}"; + } + else if (paymentState.IsCaseInsensitiveEqual("Abandoned")) + { + sellerNote = "{\"SandboxSimulation\":{\"PaymentAuthenticationStatus\":{\"State\":\"Abandoned\"}}}"; + } + + var result = _apiService.SetOrderReferenceDetails(null, settings, state, sellerNote, out var errorMesage); + + var sb = new StringBuilder(); + sb.Append(result ? "ok. " : "failure. "); + sb.AppendLine(sellerNote); + sb.AppendLine(errorMesage.EmptyNull()); + + return Content(sb.ToString()); + } + + #region Authentication + + [ChildActionOnly] public ActionResult AuthenticationPublicInfo() { var model = _apiService.CreateViewModel(AmazonPayRequestType.AuthenticationPublicInfo, TempData); @@ -146,15 +219,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: @@ -178,5 +252,7 @@ public ActionResult AuthenticationButtonHandler() return new RedirectResult(Url.LogOn(returnUrl)); } } - } + + #endregion + } } diff --git a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs index e90edbd309..2bebbbaf81 100644 --- a/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs +++ b/src/Plugins/SmartStore.AmazonPay/Controllers/AmazonPayShoppingCartController.cs @@ -3,9 +3,9 @@ namespace SmartStore.AmazonPay.Controllers { - public class AmazonPayShoppingCartController : AmazonPayControllerBase + public class AmazonPayShoppingCartController : AmazonPayControllerBase { - private readonly IAmazonPayService _apiService; + private readonly IAmazonPayService _apiService; public AmazonPayShoppingCartController(IAmazonPayService apiService) { @@ -27,6 +27,7 @@ public ActionResult ShoppingCart() return GetActionResult(model); } + return new EmptyResult(); } @@ -53,5 +54,5 @@ public ActionResult MiniShoppingCart(bool renderAmazonPayView) } return new EmptyResult(); } - } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 642b86adcc..7449e587bb 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.2.2 +MinAppVersion: 3.2.2 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..3d6e180d4f --- /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 + { + 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..baa01c2299 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: